From de86bc47cf564c486fa96446f11e05511a2ad00c Mon Sep 17 00:00:00 2001 From: pallaoro Date: Fri, 2 Oct 2026 13:27:28 +0200 Subject: [PATCH] Mission Control: switch between Tasks and Loops A Tasks / Loops switch beside the view tabs picks which work the grid tiles: the sidebar's tasks, or the sessions the project's loops own. The two never share a grid. Loops mode follows the LOOPS list order, both modes share the locked tile order, and the choice is remembered. --- apps/desktop/src/renderer/src/App.tsx | 88 +++++++++++++++++++++---- apps/desktop/src/renderer/src/index.css | 12 ++++ 2 files changed, 88 insertions(+), 12 deletions(-) diff --git a/apps/desktop/src/renderer/src/App.tsx b/apps/desktop/src/renderer/src/App.tsx index 2c371cb..e546300 100644 --- a/apps/desktop/src/renderer/src/App.tsx +++ b/apps/desktop/src/renderer/src/App.tsx @@ -86,7 +86,13 @@ import { useGithubIssues } from "./useGithubIssues"; import { activeTerminal, sessionTabs, taskGlyphs } from "./session-tabs"; import { matchesTagQuery, tagsFor, taskIcon } from "./task-tags"; import { byWhatsNext, relativeAge } from "./triage-order"; -import { type Alias, aliasLabel, type EngineMember, type UnifiedProject, unifyProjects } from "./unify"; +import { + type Alias, + aliasLabel, + type EngineMember, + type UnifiedProject, + unifyProjects, +} from "./unify"; const COLUMNS: { key: KanbanColumn; label: string }[] = [ { key: "todo", label: "Backlog" }, @@ -206,6 +212,15 @@ export function App() { localStorage.setItem("ateam.mcLock", v ? "1" : "0"); setMcLockedState(v); }; + // Which work Mission Control tiles: the tasks (the sidebar's TASKS list) or + // the loops' own tasks (its LOOPS list), never both on one grid. + const [mcMode, setMcModeState] = useState<"tasks" | "loops">(() => + localStorage.getItem("ateam.mcMode") === "loops" ? "loops" : "tasks", + ); + const setMcMode = (m: "tasks" | "loops") => { + localStorage.setItem("ateam.mcMode", m); + setMcModeState(m); + }; // The Mission Control tile with the white edge: set by clicking a task in the // sidebar or by clicking into a tile. A sidebar click also sends a jump (seq // makes a repeat click on the same task a fresh request) so the grid pages to @@ -684,14 +699,29 @@ export function App() { setMcOrder([]); } }, [activeProjectId]); + // Loops mode follows the sidebar's LOOPS list the way Tasks mode follows TASKS. + const loopOrderIds = useMemo( + () => activeLoops.flatMap((l) => (l.taskId ? [l.taskId] : [])), + [activeLoops], + ); + const missionTasks = useMemo( + () => + mcMode === "loops" + ? activeTasks.filter((t) => loopTaskIds.has(t.id)) + : activeTasks.filter((t) => !loopTaskIds.has(t.id)), + [mcMode, activeTasks, loopTaskIds], + ); const missionOrderIds = useMemo(() => { - if (!mcLocked || mcOrder.length === 0) return sidebarOrderIds; + const base = mcMode === "loops" ? loopOrderIds : sidebarOrderIds; + if (!mcLocked || mcOrder.length === 0) return base; // Drop tasks that no longer exist so the saved order can't grow forever. + // Both modes share the saved order: the other mode's ids stay in it, + // tile-less here, so a drag in one mode keeps the other's arrangement. const live = new Set(activeTasks.map((t) => t.id)); const kept = mcOrder.filter((id) => live.has(id)); const known = new Set(kept); - return [...kept, ...sidebarOrderIds.filter((id) => !known.has(id))]; - }, [mcLocked, mcOrder, sidebarOrderIds, activeTasks]); + return [...kept, ...base.filter((id) => !known.has(id))]; + }, [mcMode, mcLocked, mcOrder, loopOrderIds, sidebarOrderIds, activeTasks]); const reorderMission = (ids: string[]) => { setMcOrder(ids); if (activeProjectId) @@ -1432,6 +1462,28 @@ export function App() { Loops + {/* Left of the centered search, beside the tab it belongs to: the + right side is already full of layout buttons. */} + {mcGridShown && ( +
+ {( + [ + ["tasks", "Tasks"], + ["loops", "Loops"], + ] as const + ).map(([mode, label]) => ( + + ))} +
+ )} {/* Centered task search — absolutely centered in the topbar so the tabs on the left and action buttons on the right don't shift it. */} ; return (
-
- No live agents yet. -
- Launch agents from the Board to watch them work side by side here. -
+ {mode === "loops" ? ( +
+ No loop sessions running. +
A loop shows up here while its session is live. +
+ ) : ( +
+ No live agents yet. +
+ Launch agents from the Board to watch them work side by side here. +
+ )}
); } diff --git a/apps/desktop/src/renderer/src/index.css b/apps/desktop/src/renderer/src/index.css index 6878e53..d43b918 100644 --- a/apps/desktop/src/renderer/src/index.css +++ b/apps/desktop/src/renderer/src/index.css @@ -1474,6 +1474,18 @@ input { background: var(--bg-elev-2); color: var(--text); } +/* text variant: the Tasks / Loops switch */ +.mcmodes { + margin-left: 8px; +} +.navbtn.mcmode { + height: 26px; + padding: 0 9px; +} +.navbtn.mcmode.active { + background: var(--bg-elev-2); + color: var(--text); +} .mc .tile { position: relative; /* Square and flush; highlighting only recolors this edge, so the tile never