diff --git a/change-logs/2026/06/09/feature-sidebar-attention-mode.md b/change-logs/2026/06/09/feature-sidebar-attention-mode.md new file mode 100644 index 00000000..176f3025 --- /dev/null +++ b/change-logs/2026/06/09/feature-sidebar-attention-mode.md @@ -0,0 +1 @@ +Added an "attention" mode to the Active Tasks sidebar. The project/global scope toggle was replaced by three icon buttons — folder (this project), globe (all projects), and a new bell. Clicking the bell filters the list cross-project to only tasks in `user-questions` or `review-by-user` status, sorted oldest-first. The bell shows a pulsing count badge when tasks are waiting and the mode is not already active. diff --git a/src/mainview/components/ActiveTasksSidebar.tsx b/src/mainview/components/ActiveTasksSidebar.tsx index 2d67858d..50f92d5f 100644 --- a/src/mainview/components/ActiveTasksSidebar.tsx +++ b/src/mainview/components/ActiveTasksSidebar.tsx @@ -14,13 +14,16 @@ import AgentLauncherBadge from "./AgentLauncherBadge"; import VariantDots from "./VariantDots"; import { getTaskAgentMeta } from "../utils/taskAgentMeta"; -type SidebarScope = "project" | "global"; +type SidebarScope = "project" | "global" | "attention"; const LS_SIDEBAR_SCOPE = "dev3-sidebar-scope"; +/** Statuses that require the user's attention — the "attention" scope shows only these. */ +const ATTENTION_STATUSES: TaskStatus[] = ["user-questions", "review-by-user"]; + function readScope(): SidebarScope { try { const v = localStorage.getItem(LS_SIDEBAR_SCOPE); - if (v === "global" || v === "project") return v; + if (v === "global" || v === "project" || v === "attention") return v; } catch { /* ignore */ } return "project"; } @@ -96,9 +99,9 @@ function ActiveTasksSidebar({ return () => window.removeEventListener("keydown", handleKeyDown); }, [disableGlobalFindShortcut]); - // Fetch active tasks from all projects when in global scope. + // Fetch active tasks from all projects when in global or attention scope. useEffect(() => { - if (scope !== "global") return; + if (scope !== "global" && scope !== "attention") return; let cancelled = false; setGlobalLoading(true); (async () => { @@ -125,7 +128,7 @@ function ActiveTasksSidebar({ // Keep global tasks live across all projects. useEffect(() => { - if (scope !== "global") return; + if (scope !== "global" && scope !== "attention") return; function onTaskUpdated(e: Event) { const { task } = (e as CustomEvent).detail as { task: Task }; setGlobalTasks((prev) => { @@ -151,9 +154,24 @@ function ActiveTasksSidebar({ return () => window.removeEventListener("rpc:taskUpdated", onTaskUpdated); }, [scope]); - const sourceTasks = scope === "global" ? globalTasks : tasks; + const sourceTasks = (scope === "global" || scope === "attention") ? globalTasks : tasks; + + // Count of attention tasks across all available data (global when loaded, else project). + const attentionCount = useMemo(() => { + const pool = globalTasks.length > 0 ? globalTasks : tasks; + return pool.filter((t) => ATTENTION_STATUSES.includes(t.status)).length; + }, [globalTasks, tasks]); let activeTasks = sourceTasks.filter((task) => ACTIVE_STATUSES.includes(task.status)); + if (scope === "attention") { + activeTasks = activeTasks.filter((task) => ATTENTION_STATUSES.includes(task.status)); + // Sort oldest-first so the longest-waiting task is always at the top. + activeTasks = activeTasks.slice().sort((a, b) => { + const aTime = a.updatedAt ? new Date(a.updatedAt).getTime() : 0; + const bTime = b.updatedAt ? new Date(b.updatedAt).getTime() : 0; + return aTime - bTime; + }); + } if (searchQuery.trim()) { activeTasks = activeTasks.filter((task) => matchesSearchQuery(task, searchQuery)); } @@ -214,6 +232,7 @@ function ActiveTasksSidebar({
+ {/* Folder \u2014 this project only */} - + {/* Globe \u2014 all projects */} + {/* Bell \u2014 attention mode: cross-project, filtered to tasks needing user input */} +