From 2da914b6e7dc4c182bd20c4386aad9713e57b3aa Mon Sep 17 00:00:00 2001 From: Arjun <6592213+arkml@users.noreply.github.com> Date: Tue, 19 May 2026 19:19:00 +0530 Subject: [PATCH] move tasks to main side bar --- apps/x/apps/renderer/src/App.tsx | 51 +++++- .../renderer/src/components/bg-tasks-view.tsx | 19 +- .../src/components/sidebar-content.tsx | 162 ++++++++---------- 3 files changed, 134 insertions(+), 98 deletions(-) diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index 2dad7ae2..58673c85 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -1402,6 +1402,13 @@ function App() { loadBackgroundTasks() } + // Reload bg-task summaries if anything under bg-tasks/ changed + if ( + eventPaths.some((p) => p === 'bg-tasks' || p.startsWith('bg-tasks/')) + ) { + loadBgTaskSummaries() + } + // Invalidate cached content for files changed outside the active editor. // This prevents stale backlinks after rename-rewrite passes touch many files. for (const path of eventPaths) { @@ -1747,6 +1754,37 @@ function App() { loadRuns() }, [loadRuns]) + const [bgTaskSummaries, setBgTaskSummaries] = useState>([]) + const [bgTaskInitialSlug, setBgTaskInitialSlug] = useState(null) + const [bgTaskSlugVersion, setBgTaskSlugVersion] = useState(0) + + const loadBgTaskSummaries = useCallback(async () => { + try { + const result = await window.ipc.invoke('bg-task:list', { limit: 200 }) + setBgTaskSummaries(result.items.map((it) => ({ + slug: it.slug, + name: it.name, + active: it.active, + createdAt: it.createdAt, + lastAttemptAt: it.lastAttemptAt, + lastRunAt: it.lastRunAt, + }))) + } catch (err) { + console.error('Failed to load bg-task summaries:', err) + } + }, []) + + useEffect(() => { + loadBgTaskSummaries() + }, [loadBgTaskSummaries]) + // Load background tasks const loadBackgroundTasks = useCallback(async () => { try { @@ -5069,8 +5107,12 @@ function App() { void navigateToView({ type: 'chat-history' }) }, }} - backgroundTasks={backgroundTasks} - selectedBackgroundTask={selectedBackgroundTask} + bgTaskSummaries={bgTaskSummaries} + onOpenBgTask={(slug) => { + setBgTaskInitialSlug(slug) + setBgTaskSlugVersion((v) => v + 1) + openBgTasksView() + }} isSearchOpen={isSearchOpen} isBrowserOpen={isBrowserOpen} onToggleBrowser={handleToggleBrowser} @@ -5078,8 +5120,7 @@ function App() { onOpenSuggestedTopics={() => void navigateToView({ type: 'suggested-topics' })} isMeetingsOpen={isMeetingsOpen} onOpenMeetings={openMeetingsView} - isBgTasksOpen={isBgTasksOpen} - onOpenBgTasks={openBgTasksView} + onOpenBgTasks={() => { setBgTaskInitialSlug(null); setBgTaskSlugVersion((v) => v + 1); openBgTasksView() }} isEmailOpen={isEmailOpen} onOpenEmail={openEmailView} /> @@ -5246,6 +5287,8 @@ function App() { ) : isBgTasksOpen ? (
{ submitFromPalette(buildBgTaskSetupPrompt(description), null) }} diff --git a/apps/x/apps/renderer/src/components/bg-tasks-view.tsx b/apps/x/apps/renderer/src/components/bg-tasks-view.tsx index bb972eff..7f370355 100644 --- a/apps/x/apps/renderer/src/components/bg-tasks-view.tsx +++ b/apps/x/apps/renderer/src/components/bg-tasks-view.tsx @@ -1419,6 +1419,18 @@ export interface BgTasksViewProps { * "Edit with Copilot" button in the detail-view sidebar footer. */ onEditWithCopilot?: (slug: string) => void + /** + * If provided, the view opens with this task already selected. Updates to + * this prop sync into internal state so the sidebar can swap which task is + * focused without remounting the view. + */ + initialSlug?: string | null + /** + * Bump this counter to force a re-focus on `initialSlug` even when the + * slug value itself didn't change (e.g. user clicks the same task in the + * sidebar twice after navigating away inside the view). + */ + slugVersion?: number } function formatLastRanLabel(iso: string | null | undefined): string { @@ -1426,9 +1438,12 @@ function formatLastRanLabel(iso: string | null | undefined): string { return formatRelativeTime(iso) || 'Never' } -export function BgTasksView({ onCreateWithCopilot, onEditWithCopilot }: BgTasksViewProps = {}) { +export function BgTasksView({ onCreateWithCopilot, onEditWithCopilot, initialSlug, slugVersion }: BgTasksViewProps = {}) { const [items, setItems] = useState([]) - const [selectedSlug, setSelectedSlug] = useState(null) + const [selectedSlug, setSelectedSlug] = useState(initialSlug ?? null) + useEffect(() => { + setSelectedSlug(initialSlug ?? null) + }, [initialSlug, slugVersion]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [showNewDialog, setShowNewDialog] = useState(false) diff --git a/apps/x/apps/renderer/src/components/sidebar-content.tsx b/apps/x/apps/renderer/src/components/sidebar-content.tsx index 1fb4445a..cafa7218 100644 --- a/apps/x/apps/renderer/src/components/sidebar-content.tsx +++ b/apps/x/apps/renderer/src/components/sidebar-content.tsx @@ -17,7 +17,6 @@ import { Mic, Plug, Lightbulb, - ListChecks, LoaderIcon, Mail, Settings, @@ -124,21 +123,13 @@ type RunListItem = { agentId: string } -type BackgroundTaskItem = { +type TaskSummary = { + slug: string name: string - description?: string - schedule: { - type: "cron" | "window" | "once" - expression?: string - cron?: string - startTime?: string - endTime?: string - runAt?: string - } - enabled: boolean - status?: "scheduled" | "running" | "finished" | "failed" | "triggered" - nextRunAt?: string | null - lastRunAt?: string | null + active: boolean + createdAt: string + lastAttemptAt?: string + lastRunAt?: string } type ServiceEventType = z.infer @@ -195,8 +186,8 @@ type SidebarContentPanelProps = { currentRunId?: string | null processingRunIds?: Set tasksActions?: TasksActions - backgroundTasks?: BackgroundTaskItem[] - selectedBackgroundTask?: string | null + bgTaskSummaries?: TaskSummary[] + onOpenBgTask?: (slug: string) => void isSearchOpen?: boolean isBrowserOpen?: boolean onToggleBrowser?: () => void @@ -204,7 +195,6 @@ type SidebarContentPanelProps = { onOpenSuggestedTopics?: () => void isMeetingsOpen?: boolean onOpenMeetings?: () => void - isBgTasksOpen?: boolean onOpenBgTasks?: () => void isEmailOpen?: boolean onOpenEmail?: () => void @@ -444,8 +434,8 @@ export function SidebarContentPanel({ currentRunId, processingRunIds, tasksActions, - backgroundTasks = [], - selectedBackgroundTask, + bgTaskSummaries = [], + onOpenBgTask, isSearchOpen = false, isBrowserOpen = false, onToggleBrowser, @@ -453,7 +443,6 @@ export function SidebarContentPanel({ onOpenSuggestedTopics, isMeetingsOpen = false, onOpenMeetings, - isBgTasksOpen = false, onOpenBgTasks, isEmailOpen = false, onOpenEmail, @@ -471,7 +460,6 @@ export function SidebarContentPanel({ const isBrowserQuickActionSelected = isBrowserOpen && !isSearchOpen const isSuggestedTopicsQuickActionSelected = isSuggestedTopicsOpen && !isBrowserOpen const isMeetingsQuickActionSelected = isMeetingsOpen && !isBrowserOpen - const isBgTasksQuickActionSelected = isBgTasksOpen && !isBrowserOpen const isEmailQuickActionSelected = isEmailOpen && !isBrowserOpen const handleRowboatLogin = useCallback(async () => { @@ -567,24 +555,14 @@ export function SidebarContentPanel({ Meetings )} - {onOpenBgTasks && ( - - )}
+ {/* Billing / upgrade CTA or Log in CTA */} @@ -1124,25 +1100,60 @@ export function WorkspaceSection({ } -// Get status indicator color -function getStatusColor(status?: string, enabled?: boolean): string { - // Disabled agents always show gray - if (enabled === false) { - return "bg-gray-400" - } - switch (status) { - case "running": - return "bg-blue-500" - case "finished": - return "bg-green-500" - case "failed": - return "bg-red-500" - case "triggered": - return "bg-gray-400" - case "scheduled": - default: - return "bg-yellow-500" - } +function TasksSidebarSection({ + tasks, + onOpenTask, + onOpenTasksView, +}: { + tasks: TaskSummary[] + onOpenTask?: (slug: string) => void + onOpenTasksView?: () => void +}) { + const recentTasks = React.useMemo(() => { + const toTime = (s?: string | null): number => { + if (!s) return 0 + const t = new Date(s).getTime() + return Number.isNaN(t) ? 0 : t + } + const activity = (t: TaskSummary): number => + Math.max(toTime(t.lastRunAt), toTime(t.lastAttemptAt), toTime(t.createdAt)) + return [...tasks] + .sort((a, b) => activity(b) - activity(a)) + .slice(0, 3) + }, [tasks]) + + return ( + +
+ Tasks +
+ + + {recentTasks.map((task) => ( + + onOpenTask?.(task.slug)} + className="gap-2" + > + + + {task.name} + + + + ))} + {onOpenTasksView && ( + + + + View all + + + )} + + +
+ ) } // Tasks Section @@ -1151,51 +1162,18 @@ function TasksSection({ currentRunId, processingRunIds, actions, - backgroundTasks = [], - selectedBackgroundTask, }: { runs: RunListItem[] currentRunId?: string | null processingRunIds?: Set actions?: TasksActions - backgroundTasks?: BackgroundTaskItem[] - selectedBackgroundTask?: string | null }) { const [pendingDeleteRunId, setPendingDeleteRunId] = useState(null) return ( - {/* Background Tasks Section */} - {backgroundTasks.length > 0 && ( - <> -
- Background Tasks -
- - {backgroundTasks.map((task) => ( - - actions?.onSelectBackgroundTask?.(task.name)} - className="gap-2" - > -
- - -
- - {task.name} - -
-
- ))} -
- - )} -
+
Chat history