Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 670032f

Browse files
committed
feat: implement Phase 6b Tab/Panel Switching for background tasks
Add a dedicated BackgroundTaskView as a full tab in the webview, with a new tab bar icon entry point. Users can navigate between BackgroundTasksList (showing all subtasks with status badges, mode, and timestamps) and BackgroundTaskReplayView (from Phase 6a) without disrupting the foreground task state. Changes: - Add backgroundTasksButtonClicked command type and registration - Add BackgroundTaskView container component with list/replay sub-views - Add BackgroundTasksList component showing subtasks from task history - Add bgTask tab type to App.tsx routing - Register command in package.json with sidebar and editor menu entries - 30 tests across 5 test files (all passing) Issue #12330 Phase 6b
1 parent 770a220 commit 670032f

10 files changed

Lines changed: 604 additions & 4 deletions

File tree

packages/types/src/vscode-extension-host.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ export interface ExtensionMessage {
105105
| "chatButtonClicked"
106106
| "settingsButtonClicked"
107107
| "historyButtonClicked"
108+
| "backgroundTasksButtonClicked"
108109
| "didBecomeVisible"
109110
| "focusInput"
110111
| "switchTab"
@@ -529,7 +530,7 @@ export interface WebviewMessage {
529530
text?: string
530531
taskId?: string
531532
editedMessageContent?: string
532-
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "bgTaskReplay"
533+
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "bgTaskReplay" | "bgTask"
533534
disabled?: boolean
534535
context?: string
535536
dataUri?: string

packages/types/src/vscode.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export const commandIds = [
4545
"acceptInput",
4646
"focusPanel",
4747
"toggleAutoApprove",
48+
"backgroundTasksButtonClicked",
4849
] as const
4950

5051
export type CommandId = (typeof commandIds)[number]

src/activate/registerCommands.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,15 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
108108

109109
visibleProvider.postMessageToWebview({ type: "action", action: "historyButtonClicked" })
110110
},
111+
backgroundTasksButtonClicked: () => {
112+
const visibleProvider = getVisibleProviderOrLog(outputChannel)
113+
114+
if (!visibleProvider) {
115+
return
116+
}
117+
118+
visibleProvider.postMessageToWebview({ type: "action", action: "backgroundTasksButtonClicked" })
119+
},
111120
newTask: handleNewTask,
112121
setCustomStoragePath: async () => {
113122
const { promptForCustomStoragePath } = await import("../utils/storage")

src/package.json

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,11 @@
159159
"command": "roo-cline.toggleAutoApprove",
160160
"title": "%command.toggleAutoApprove.title%",
161161
"category": "%configuration.title%"
162+
},
163+
{
164+
"command": "roo-cline.backgroundTasksButtonClicked",
165+
"title": "Background Tasks",
166+
"icon": "$(server-process)"
162167
}
163168
],
164169
"menus": {
@@ -219,9 +224,14 @@
219224
"when": "view == roo-cline.SidebarProvider"
220225
},
221226
{
222-
"command": "roo-cline.popoutButtonClicked",
227+
"command": "roo-cline.backgroundTasksButtonClicked",
223228
"group": "overflow@2",
224229
"when": "view == roo-cline.SidebarProvider"
230+
},
231+
{
232+
"command": "roo-cline.popoutButtonClicked",
233+
"group": "overflow@3",
234+
"when": "view == roo-cline.SidebarProvider"
225235
}
226236
],
227237
"editor/title": [
@@ -241,9 +251,14 @@
241251
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
242252
},
243253
{
244-
"command": "roo-cline.popoutButtonClicked",
254+
"command": "roo-cline.backgroundTasksButtonClicked",
245255
"group": "overflow@2",
246256
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
257+
},
258+
{
259+
"command": "roo-cline.popoutButtonClicked",
260+
"group": "overflow@3",
261+
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
247262
}
248263
]
249264
},

webview-ui/src/App.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ import HistoryView from "./components/history/HistoryView"
1313
import SettingsView, { SettingsViewRef } from "./components/settings/SettingsView"
1414
import WelcomeView from "./components/welcome/WelcomeViewProvider"
1515
import BackgroundTaskReplayView from "./components/chat/BackgroundTaskReplayView"
16+
import BackgroundTaskView from "./components/chat/BackgroundTaskView"
1617
import { CheckpointRestoreDialog } from "./components/chat/CheckpointRestoreDialog"
1718
import { DeleteMessageDialog, EditMessageDialog } from "./components/chat/MessageModificationConfirmationDialog"
1819
import ErrorBoundary from "./components/ErrorBoundary"
1920
import { useAddNonInteractiveClickListener } from "./components/ui/hooks/useNonInteractiveClick"
2021
import { TooltipProvider } from "./components/ui/tooltip"
2122
import { STANDARD_TOOLTIP_DELAY } from "./components/ui/standard-tooltip"
2223

23-
type Tab = "settings" | "history" | "chat" | "bgTaskReplay"
24+
type Tab = "settings" | "history" | "chat" | "bgTaskReplay" | "bgTask"
2425

2526
interface DeleteMessageDialogState {
2627
isOpen: boolean
@@ -44,6 +45,7 @@ const tabsByMessageAction: Partial<Record<NonNullable<ExtensionMessage["action"]
4445
chatButtonClicked: "chat",
4546
settingsButtonClicked: "settings",
4647
historyButtonClicked: "history",
48+
backgroundTasksButtonClicked: "bgTask",
4749
}
4850

4951
const App = () => {
@@ -189,6 +191,7 @@ const App = () => {
189191
}}
190192
/>
191193
)}
194+
{tab === "bgTask" && <BackgroundTaskView onClose={() => switchTab("chat")} />}
192195
{tab === "history" && <HistoryView onDone={() => switchTab("chat")} />}
193196
{tab === "settings" && (
194197
<SettingsView ref={settingsRef} onDone={() => setTab("chat")} targetSection={currentSection} />

webview-ui/src/__tests__/App.spec.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,24 @@ vi.mock("@src/components/history/HistoryView", () => ({
4040
},
4141
}))
4242

43+
vi.mock("@src/components/chat/BackgroundTaskView", () => ({
44+
__esModule: true,
45+
default: function BackgroundTaskView({ onClose }: { onClose: () => void }) {
46+
return (
47+
<div data-testid="background-task-view" onClick={onClose}>
48+
Background Task View
49+
</div>
50+
)
51+
},
52+
}))
53+
54+
vi.mock("@src/components/chat/BackgroundTaskReplayView", () => ({
55+
__esModule: true,
56+
default: function BackgroundTaskReplayView() {
57+
return <div data-testid="background-task-replay-view">Background Task Replay View</div>
58+
},
59+
}))
60+
4361
vi.mock("@src/components/mcp/McpView", () => ({
4462
__esModule: true,
4563
default: function McpView() {
@@ -206,6 +224,38 @@ describe("App", () => {
206224
expect(screen.queryByTestId("settings-view")).not.toBeInTheDocument()
207225
})
208226

227+
it("switches to background tasks view when receiving backgroundTasksButtonClicked action", async () => {
228+
render(<AppWithProviders />)
229+
230+
act(() => {
231+
triggerMessage("backgroundTasksButtonClicked")
232+
})
233+
234+
const bgTaskView = await screen.findByTestId("background-task-view")
235+
expect(bgTaskView).toBeInTheDocument()
236+
237+
const chatView = screen.getByTestId("chat-view")
238+
expect(chatView.getAttribute("data-hidden")).toBe("true")
239+
})
240+
241+
it("returns to chat view when clicking done in background tasks view", async () => {
242+
render(<AppWithProviders />)
243+
244+
act(() => {
245+
triggerMessage("backgroundTasksButtonClicked")
246+
})
247+
248+
const bgTaskView = await screen.findByTestId("background-task-view")
249+
250+
act(() => {
251+
bgTaskView.click()
252+
})
253+
254+
const chatView = screen.getByTestId("chat-view")
255+
expect(chatView.getAttribute("data-hidden")).toBe("false")
256+
expect(screen.queryByTestId("background-task-view")).not.toBeInTheDocument()
257+
})
258+
209259
it.each(["history"])("returns to chat view when clicking done in %s view", async (view) => {
210260
render(<AppWithProviders />)
211261

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { memo, useCallback, useState } from "react"
2+
import { ArrowLeft } from "lucide-react"
3+
4+
import BackgroundTasksList from "./BackgroundTasksList"
5+
import BackgroundTaskReplayView from "./BackgroundTaskReplayView"
6+
7+
type BackgroundTaskSubView = "list" | "replay"
8+
9+
export interface BackgroundTaskViewProps {
10+
onClose: () => void
11+
}
12+
13+
/**
14+
* Full-tab container for the background tasks feature (Phase 6b).
15+
* Manages navigation between BackgroundTasksList and BackgroundTaskReplayView.
16+
* Later, BackgroundTaskLiveView (Phase 6c) will be added as another sub-view.
17+
*/
18+
const BackgroundTaskView = memo(({ onClose }: BackgroundTaskViewProps) => {
19+
const [subView, setSubView] = useState<BackgroundTaskSubView>("list")
20+
const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null)
21+
22+
const handleSelectTask = useCallback((taskId: string) => {
23+
setSelectedTaskId(taskId)
24+
setSubView("replay")
25+
}, [])
26+
27+
const handleBackToList = useCallback(() => {
28+
setSelectedTaskId(null)
29+
setSubView("list")
30+
}, [])
31+
32+
return (
33+
<div className="flex flex-col h-full" data-testid="background-task-view">
34+
{/* Top header bar -- only shown in list view since replay has its own header */}
35+
{subView === "list" && (
36+
<div
37+
className="flex items-center gap-2 px-4 py-2 border-b"
38+
style={{
39+
borderColor: "var(--vscode-panel-border)",
40+
backgroundColor: "var(--vscode-sideBar-background)",
41+
}}
42+
data-testid="background-task-view-header">
43+
<button
44+
onClick={onClose}
45+
className="flex items-center gap-1 text-vscode-textLink-foreground hover:underline cursor-pointer bg-transparent border-none p-0"
46+
data-testid="background-task-view-back">
47+
<ArrowLeft size={16} />
48+
<span>Back to chat</span>
49+
</button>
50+
<span className="text-vscode-descriptionForeground text-sm ml-2 font-medium">Background Tasks</span>
51+
</div>
52+
)}
53+
54+
{/* Sub-view content */}
55+
<div className="flex-1 overflow-hidden">
56+
{subView === "list" && <BackgroundTasksList onSelectTask={handleSelectTask} />}
57+
{subView === "replay" && selectedTaskId && (
58+
<BackgroundTaskReplayView taskId={selectedTaskId} onClose={handleBackToList} />
59+
)}
60+
</div>
61+
</div>
62+
)
63+
})
64+
65+
BackgroundTaskView.displayName = "BackgroundTaskView"
66+
67+
export default BackgroundTaskView

0 commit comments

Comments
 (0)