Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions apps/web/src/components/Sidebar.logic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/Sidebar.logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
36 changes: 19 additions & 17 deletions apps/web/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,15 @@ import {
CircleCheckIcon,
CircleDashedIcon,
ClockIcon,
EyeIcon,
FolderIcon,
GitBranchIcon,
MessageCircleQuestionIcon,
PinIcon,
PinOffIcon,
PlusIcon,
SettingsIcon,
ShieldQuestionIcon,
SquarePenIcon,
TerminalIcon,
Undo2Icon,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
: 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
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -1814,6 +1808,14 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
>
{topStatus.icon === "working" ? (
<CircleDashedIcon aria-hidden className="size-4 shrink-0" />
) : topStatus.icon === "input" ? (
<MessageCircleQuestionIcon aria-hidden className="size-4 shrink-0" />
) : topStatus.icon === "approval" ? (
<ShieldQuestionIcon aria-hidden className="size-4 shrink-0" />
) : topStatus.icon === "failed" ? (
<CircleAlertIcon aria-hidden className="size-4 shrink-0" />
) : topStatus.icon === "monitoring" ? (
<EyeIcon aria-hidden className="size-4 shrink-0" />
) : topStatus.icon === "done" ? (
<CircleCheckIcon aria-hidden className="size-4 shrink-0" />
) : null}
Expand Down
Loading