authorgravatar for maria@kuuro.netmaria <maria@kuuro.net> 2026-09-12 18:38:07-03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-12 18:38:07-03:00
log03e135577cbf861c2948981601c070dab038ea11
tree1625618af5c9f2ac166874a6f9cbbb563844d408
parentcfeaca41ae27bdf2c203158d378c87c7308fea2a
signature Signed by PGP key B5690EEEBB952194

fix(web): complete thread status icons and keep input threads prominent (#11461)

Co-authored-by: maria-rcks <254055478+maria-rcks@users.noreply.github.com>

3 files changed, 33 insertions(+), 19 deletions(-)

apps/web/src/components/Sidebar.logic.test.ts+12
......@@ -395,6 +395,18 @@ describe("shouldRecedeSidebarThread", () => {
395395 expect(shouldRecedeSidebarThread({ ...input, isActive: true })).toBe(false);
396396 expect(shouldRecedeSidebarThread({ ...input, isSelected: true })).toBe(false);
397397 });
398
399 it.each([false, true])("keeps input-required threads prominent with unread=%s", (isUnread) => {
400 expect(
401 shouldRecedeSidebarThread({
402 status: "input",
403 isUnread,
404 isWoke: false,
405 isActive: false,
406 isSelected: false,
407 }),
408 ).toBe(false);
409 });
398410});
399411
400412describe("createThreadJumpHintVisibilityController", () => {
apps/web/src/components/Sidebar.logic.ts+2-2
......@@ -803,9 +803,9 @@ export function shouldRecedeSidebarThread(input: {
803803 isActive: boolean;
804804 isSelected: boolean;
805805}): boolean {
806 if (input.isActive || input.isSelected) return false;
806 if (input.isActive || input.isSelected || input.status === "input") return false;
807807 if (input.status === "working" || input.status === "monitoring") return true;
808 if (input.status === "ready" || input.status === "approval" || input.status === "input") {
808 if (input.status === "ready" || input.status === "approval") {
809809 return !input.isUnread && !input.isWoke;
810810 }
811811 return false;
apps/web/src/components/Sidebar.tsx+19-17
......@@ -45,12 +45,15 @@ import {
4545 CircleCheckIcon,
4646 CircleDashedIcon,
4747 ClockIcon,
48 EyeIcon,
4849 FolderIcon,
4950 GitBranchIcon,
51 MessageCircleQuestionIcon,
5052 PinIcon,
5153 PinOffIcon,
5254 PlusIcon,
5355 SettingsIcon,
56 ShieldQuestionIcon,
5457 SquarePenIcon,
5558 TerminalIcon,
5659 Undo2Icon,
......@@ -1088,8 +1091,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
10881091 // switching sidebars must not light up every historical thread as unread.
10891092 const isUnread = hasUnseenCompletion({ ...thread, lastVisitedAt });
10901093 const status = resolveSidebarThreadStatus(thread);
1091 const isInFlight =
1092 status === "working" || status === "monitoring" || status === "approval" || status === "input";
10931094 // A woken thread reappears at its original position (the sort is
10941095 // deliberately static), so the pill has to carry the weight. Snoozing is
10951096 // an explicit act, so the pill clears only when the user re-engages:
......@@ -1123,35 +1124,32 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
11231124 icon: "working" as const,
11241125 // No shimmer: a label that animates forever is noise in a sidebar
11251126 // full of them (and repaints every vsync on high-refresh displays).
1126 // Working is a background state, so it rests at the dim end of what
1127 // the old pulse cycled through; only the thread you have open gets
1128 // the label at full strength.
1129 className: cn("text-sky-600 dark:text-sky-400", !props.isActive && "opacity-75"),
1127 className: "text-sky-600 dark:text-sky-400",
11301128 }
11311129 : status === "monitoring"
11321130 ? {
11331131 // Monitoring is calm background presence, not active progress
11341132 // (monitoring-pill D6), so it keeps the label at full strength.
11351133 label: "Monitoring",
1136 icon: null,
1137 className: "text-sky-600 dark:text-sky-400",
1134 icon: "monitoring" as const,
1135 className: "text-foreground dark:text-white",
11381136 }
11391137 : status === "approval"
11401138 ? {
11411139 label: "Approval",
1142 icon: null,
1140 icon: "approval" as const,
11431141 className: "text-amber-700 dark:text-amber-300",
11441142 }
11451143 : status === "input"
11461144 ? {
11471145 label: "Input",
1148 icon: null,
1146 icon: "input" as const,
11491147 className: "text-indigo-600 dark:text-indigo-300",
11501148 }
11511149 : status === "failed"
11521150 ? {
11531151 label: "Failed",
1154 icon: null,
1152 icon: "failed" as const,
11551153 className: "text-red-700 dark:text-red-300",
11561154 }
11571155 : isWoke
......@@ -1396,10 +1394,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
13961394 : shouldRecede
13971395 ? "text-sidebar-muted-foreground/75 hover:bg-sidebar-row-hover hover:text-sidebar-foreground"
13981396 : "bg-transparent text-sidebar-foreground hover:bg-sidebar-row-hover",
1399 isInFlight &&
1400 !props.isActive &&
1401 !isSelected &&
1402 "opacity-70 transition-opacity hover:opacity-100",
14031397 isFileDragOver && "ring-1 ring-inset ring-primary/70",
14041398 // The hover tint must not clobber an active/selected row's own surface.
14051399 isFileDragOver && !props.isActive && !isSelected && "bg-sidebar-row-hover",
......@@ -1462,7 +1456,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
14621456 "truncate",
14631457 shouldRecede
14641458 ? "text-secondary-label"
1465 : isUnread || isWoke
1459 : isUnread || isWoke || status === "input"
14661460 ? "text-foreground"
14671461 : status === "failed"
14681462 ? "text-foreground/95"
......@@ -1472,7 +1466,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
14721466 "truncate group-focus-within/sidebar-row:text-foreground group-hover/sidebar-row:text-foreground",
14731467 shouldRecede
14741468 ? "text-secondary-label/70"
1475 : props.isActive || isWoke
1469 : props.isActive || isWoke || status === "input"
14761470 ? "text-foreground"
14771471 : isUnread
14781472 ? "text-muted-foreground"
......@@ -1814,6 +1808,14 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
18141808 >
18151809 {topStatus.icon === "working" ? (
18161810 <CircleDashedIcon aria-hidden className="size-4 shrink-0" />
1811 ) : topStatus.icon === "input" ? (
1812 <MessageCircleQuestionIcon aria-hidden className="size-4 shrink-0" />
1813 ) : topStatus.icon === "approval" ? (
1814 <ShieldQuestionIcon aria-hidden className="size-4 shrink-0" />
1815 ) : topStatus.icon === "failed" ? (
1816 <CircleAlertIcon aria-hidden className="size-4 shrink-0" />
1817 ) : topStatus.icon === "monitoring" ? (
1818 <EyeIcon aria-hidden className="size-4 shrink-0" />
18171819 ) : topStatus.icon === "done" ? (
18181820 <CircleCheckIcon aria-hidden className="size-4 shrink-0" />
18191821 ) : null}