| ... | ... | @@ -144,6 +144,7 @@ import { |
| 144 | 144 | resolveSidebarThreadStatus, |
| 145 | 145 | searchSidebarThreadsByTitle, |
| 146 | 146 | shouldCreateNewThreadInCurrentProject, |
| 147 | shouldRecedeSidebarThread, |
| 147 | 148 | resolveWorkingStartedAt, |
| 148 | 149 | sortLogicalProjectsForSidebar, |
| 149 | 150 | sortPinnedThreadsForSidebar, |
| ... | ... | @@ -874,6 +875,8 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 874 | 875 | // switching sidebars must not light up every historical thread as unread. |
| 875 | 876 | const isUnread = hasUnseenCompletion({ ...thread, lastVisitedAt }); |
| 876 | 877 | const status = resolveSidebarThreadStatus(thread); |
| 878 | const isInFlight = |
| 879 | status === "working" || status === "monitoring" || status === "approval" || status === "input"; |
| 877 | 880 | // A woken thread reappears at its original position (the sort is |
| 878 | 881 | // deliberately static), so the pill has to carry the weight. Snoozing is |
| 879 | 882 | // an explicit act, so the pill clears only when the user re-engages: |
| ... | ... | @@ -887,17 +890,16 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 887 | 890 | wokeAtDate !== null && |
| 888 | 891 | (lastVisitedDate === null || lastVisitedDate < wokeAtDate) && |
| 889 | 892 | thread.settledOverride !== "settled"; |
| 890 | | // In-flight rows (working, or waiting on approval/input) fade as a whole: |
| 891 | | // there is nothing for the user to do yet, so prominence is reserved for |
| 892 | | // rows that need a human — done (unread), read-but-unsettled, failed, and |
| 893 | | // freshly woken. The status label keeps its hue, so waiting rows stay |
| 894 | | // findable. In-flight rows recede the same as read-ready ones (inbox-zero: |
| 895 | | // working threads aren't your problem yet) — only the colored status label |
| 896 | | // stands out. |
| 897 | | const isInFlight = |
| 898 | | status === "working" || status === "monitoring" || status === "approval" || status === "input"; |
| 899 | | const shouldRecede = |
| 900 | | (status === "ready" || isInFlight) && !isUnread && !isWoke && !props.isActive && !isSelected; |
| 893 | // Background work always recedes when it is not selected: an unread parent |
| 894 | // completion must not pull a still-working thread back into the foreground. |
| 895 | // Ready and action-required rows keep their unread and wake prominence. |
| 896 | const shouldRecede = shouldRecedeSidebarThread({ |
| 897 | status, |
| 898 | isUnread, |
| 899 | isWoke, |
| 900 | isActive: props.isActive, |
| 901 | isSelected, |
| 902 | }); |
| 901 | 903 | // Status hues follow the system-wide convention set by sidebar v1 and the |
| 902 | 904 | // mobile Live Activity/widgets (amber approval, indigo input, sky working) |
| 903 | 905 | // so a thread reads the same color everywhere it surfaces. |
| ... | ... | @@ -1210,21 +1212,23 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { |
| 1210 | 1212 | variant === "card" |
| 1211 | 1213 | ? cn( |
| 1212 | 1214 | "truncate", |
| 1213 | | isUnread || isWoke |
| 1214 | | ? "text-foreground" |
| 1215 | | : shouldRecede |
| 1216 | | ? "text-secondary-label" |
| 1215 | shouldRecede |
| 1216 | ? "text-secondary-label" |
| 1217 | : isUnread || isWoke |
| 1218 | ? "text-foreground" |
| 1217 | 1219 | : status === "failed" |
| 1218 | 1220 | ? "text-foreground/95" |
| 1219 | 1221 | : "text-foreground/90", |
| 1220 | 1222 | ) |
| 1221 | 1223 | : cn( |
| 1222 | 1224 | "truncate group-hover/sidebar-row:text-foreground", |
| 1223 | | props.isActive || isWoke |
| 1224 | | ? "text-foreground" |
| 1225 | | : isUnread |
| 1226 | | ? "text-muted-foreground" |
| 1227 | | : "text-secondary-label/70", |
| 1225 | shouldRecede |
| 1226 | ? "text-secondary-label/70" |
| 1227 | : props.isActive || isWoke |
| 1228 | ? "text-foreground" |
| 1229 | : isUnread |
| 1230 | ? "text-muted-foreground" |
| 1231 | : "text-secondary-label/70", |
| 1228 | 1232 | ), |
| 1229 | 1233 | isRegeneratingTitle && "opacity-[0.55]", |
| 1230 | 1234 | )} |