diff --git a/webview-ui/src/components/history/HistoryPreview.tsx b/webview-ui/src/components/history/HistoryPreview.tsx index 2169b1d96e..1dd9301585 100644 --- a/webview-ui/src/components/history/HistoryPreview.tsx +++ b/webview-ui/src/components/history/HistoryPreview.tsx @@ -2,6 +2,7 @@ import { memo } from "react" import { vscode } from "@src/utils/vscode" import { useAppTranslation } from "@src/i18n/TranslationContext" +import { useExtensionState } from "@src/context/ExtensionStateContext" import { useTaskSearch } from "./useTaskSearch" import TaskItem from "./TaskItem" @@ -9,11 +10,17 @@ import TaskItem from "./TaskItem" const HistoryPreview = () => { const { tasks } = useTaskSearch() const { t } = useAppTranslation() + const { maxTasksHomeScreen } = useExtensionState() const handleViewAllHistory = () => { vscode.postMessage({ type: "switchTab", tab: "history" }) } + // If maxTasksHomeScreen is 0, don't render anything + if (maxTasksHomeScreen === 0) { + return null + } + return (
@@ -27,7 +34,7 @@ const HistoryPreview = () => {
{tasks.length !== 0 && ( <> - {tasks.slice(0, 4).map((item) => ( + {tasks.slice(0, maxTasksHomeScreen).map((item) => ( ))} diff --git a/webview-ui/src/components/history/__tests__/HistoryPreview.spec.tsx b/webview-ui/src/components/history/__tests__/HistoryPreview.spec.tsx index da344970a8..427f73de6a 100644 --- a/webview-ui/src/components/history/__tests__/HistoryPreview.spec.tsx +++ b/webview-ui/src/components/history/__tests__/HistoryPreview.spec.tsx @@ -3,8 +3,10 @@ import { render, screen } from "@/utils/test-utils" import type { HistoryItem } from "@roo-code/types" import HistoryPreview from "../HistoryPreview" +import { useExtensionState } from "@/context/ExtensionStateContext" vi.mock("../useTaskSearch") +vi.mock("@/context/ExtensionStateContext") vi.mock("../TaskItem", () => { return { @@ -20,6 +22,7 @@ import { useTaskSearch } from "../useTaskSearch" import TaskItem from "../TaskItem" const mockUseTaskSearch = useTaskSearch as any +const mockUseExtensionState = useExtensionState as any const mockTaskItem = TaskItem as any const mockTasks: HistoryItem[] = [ @@ -82,6 +85,32 @@ const mockTasks: HistoryItem[] = [ describe("HistoryPreview", () => { beforeEach(() => { vi.clearAllMocks() + // Default mock for useExtensionState + mockUseExtensionState.mockReturnValue({ + maxTasksHomeScreen: 4, + }) + }) + + it("renders nothing when maxTasksHomeScreen is 0", () => { + mockUseExtensionState.mockReturnValue({ + maxTasksHomeScreen: 0, + }) + mockUseTaskSearch.mockReturnValue({ + tasks: mockTasks, + searchQuery: "", + setSearchQuery: vi.fn(), + sortOption: "newest", + setSortOption: vi.fn(), + lastNonRelevantSort: null, + setLastNonRelevantSort: vi.fn(), + showAllWorkspaces: false, + setShowAllWorkspaces: vi.fn(), + }) + + const { container } = render() + + // Should render nothing when maxTasksHomeScreen is 0 + expect(container.firstChild).toBeNull() }) it("renders nothing when no tasks are available", () => { @@ -228,4 +257,29 @@ describe("HistoryPreview", () => { expect(container.firstChild).toHaveClass("flex", "flex-col", "gap-1") }) + + it("respects maxTasksHomeScreen setting", () => { + mockUseExtensionState.mockReturnValue({ + maxTasksHomeScreen: 2, + }) + mockUseTaskSearch.mockReturnValue({ + tasks: mockTasks, + searchQuery: "", + setSearchQuery: vi.fn(), + sortOption: "newest", + setSortOption: vi.fn(), + lastNonRelevantSort: null, + setLastNonRelevantSort: vi.fn(), + showAllWorkspaces: false, + setShowAllWorkspaces: vi.fn(), + }) + + render() + + // Should render only the first 2 tasks + expect(screen.getByTestId("task-item-task-1")).toBeInTheDocument() + expect(screen.getByTestId("task-item-task-2")).toBeInTheDocument() + expect(screen.queryByTestId("task-item-task-3")).not.toBeInTheDocument() + expect(screen.queryByTestId("task-item-task-4")).not.toBeInTheDocument() + }) }) diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 3a9cb539a8..1f8ed5ff0f 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -201,6 +201,7 @@ const SettingsView = forwardRef(({ onDone, t openRouterImageApiKey, openRouterImageGenerationSelectedModel, reasoningBlockCollapsed, + maxTasksHomeScreen, includeCurrentTime, includeCurrentCost, } = cachedState @@ -393,6 +394,7 @@ const SettingsView = forwardRef(({ onDone, t condensingApiConfigId: condensingApiConfigId || "", includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? true, reasoningBlockCollapsed: reasoningBlockCollapsed ?? true, + maxTasksHomeScreen: maxTasksHomeScreen ?? 4, includeCurrentTime: includeCurrentTime ?? true, includeCurrentCost: includeCurrentCost ?? true, profileThresholds, @@ -802,6 +804,7 @@ const SettingsView = forwardRef(({ onDone, t {activeTab === "ui" && ( )} diff --git a/webview-ui/src/components/settings/UISettings.tsx b/webview-ui/src/components/settings/UISettings.tsx index 2de16e6882..1cb3b592a6 100644 --- a/webview-ui/src/components/settings/UISettings.tsx +++ b/webview-ui/src/components/settings/UISettings.tsx @@ -1,6 +1,6 @@ import { HTMLAttributes } from "react" import { useAppTranslation } from "@/i18n/TranslationContext" -import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react" +import { VSCodeCheckbox, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" import { Glasses } from "lucide-react" import { telemetryClient } from "@/utils/TelemetryClient" @@ -11,10 +11,16 @@ import { ExtensionStateContextType } from "@/context/ExtensionStateContext" interface UISettingsProps extends HTMLAttributes { reasoningBlockCollapsed: boolean + maxTasksHomeScreen: number setCachedStateField: SetCachedStateField } -export const UISettings = ({ reasoningBlockCollapsed, setCachedStateField, ...props }: UISettingsProps) => { +export const UISettings = ({ + reasoningBlockCollapsed, + maxTasksHomeScreen, + setCachedStateField, + ...props +}: UISettingsProps) => { const { t } = useAppTranslation() const handleReasoningBlockCollapsedChange = (value: boolean) => { @@ -26,6 +32,18 @@ export const UISettings = ({ reasoningBlockCollapsed, setCachedStateField, ...pr }) } + const handleMaxTasksHomeScreenChange = (value: string) => { + const numValue = parseInt(value, 10) + if (!isNaN(numValue) && numValue >= 0 && numValue <= 20) { + setCachedStateField("maxTasksHomeScreen", numValue) + + // Track telemetry event + telemetryClient.capture("ui_settings_max_tasks_home_screen_changed", { + value: numValue, + }) + } + } + return (
@@ -49,6 +67,26 @@ export const UISettings = ({ reasoningBlockCollapsed, setCachedStateField, ...pr {t("settings:ui.collapseThinking.description")}
+ + {/* Maximum Tasks in Home Screen Setting */} +
+ + handleMaxTasksHomeScreenChange(e.target.value)} + data-testid="max-tasks-home-screen-input" + className="w-32" + /> +
+ {t("settings:ui.maxTasksHomeScreen.description")} +
+
diff --git a/webview-ui/src/components/settings/__tests__/UISettings.spec.tsx b/webview-ui/src/components/settings/__tests__/UISettings.spec.tsx index 43bb013a08..cb8d5b04f1 100644 --- a/webview-ui/src/components/settings/__tests__/UISettings.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/UISettings.spec.tsx @@ -5,6 +5,7 @@ import { UISettings } from "../UISettings" describe("UISettings", () => { const defaultProps = { reasoningBlockCollapsed: false, + maxTasksHomeScreen: 4, setCachedStateField: vi.fn(), } @@ -14,12 +15,24 @@ describe("UISettings", () => { expect(checkbox).toBeTruthy() }) - it("displays the correct initial state", () => { + it("renders the max tasks home screen input", () => { + const { getByTestId } = render() + const input = getByTestId("max-tasks-home-screen-input") + expect(input).toBeTruthy() + }) + + it("displays the correct initial state for collapse thinking", () => { const { getByTestId } = render() const checkbox = getByTestId("collapse-thinking-checkbox") as HTMLInputElement expect(checkbox.checked).toBe(true) }) + it("displays the correct initial value for max tasks", () => { + const { getByTestId } = render() + const input = getByTestId("max-tasks-home-screen-input") as HTMLInputElement + expect(input.value).toBe("10") + }) + it("calls setCachedStateField when checkbox is toggled", async () => { const setCachedStateField = vi.fn() const { getByTestId } = render() @@ -40,4 +53,13 @@ describe("UISettings", () => { rerender() expect(checkbox.checked).toBe(true) }) + + it("updates input value when maxTasksHomeScreen prop changes", () => { + const { getByTestId, rerender } = render() + const input = getByTestId("max-tasks-home-screen-input") as HTMLInputElement + expect(input.value).toBe("4") + + rerender() + expect(input.value).toBe("10") + }) }) diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 6443ccad93..868e6588fe 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -28,6 +28,7 @@ import { convertTextMateToHljs } from "@src/utils/textMateToHljs" export interface ExtensionStateContextType extends ExtensionState { historyPreviewCollapsed?: boolean // Add the new state property + maxTasksHomeScreen: number // Maximum number of tasks to show in home screen (0-20) didHydrateState: boolean showWelcome: boolean theme: any @@ -148,6 +149,7 @@ export interface ExtensionStateContextType extends ExtensionState { setTerminalCompressProgressBar: (value: boolean) => void setHistoryPreviewCollapsed: (value: boolean) => void setReasoningBlockCollapsed: (value: boolean) => void + setMaxTasksHomeScreen: (value: number) => void autoCondenseContext: boolean setAutoCondenseContext: (value: boolean) => void autoCondenseContextPercent: number @@ -250,6 +252,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode terminalCompressProgressBar: true, // Default to compress progress bar output historyPreviewCollapsed: false, // Initialize the new state (default to expanded) reasoningBlockCollapsed: true, // Default to collapsed + maxTasksHomeScreen: 4, // Default to showing 4 tasks cloudUserInfo: null, cloudIsAuthenticated: false, cloudOrganizations: [], @@ -451,6 +454,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode const contextValue: ExtensionStateContextType = { ...state, reasoningBlockCollapsed: state.reasoningBlockCollapsed ?? true, + maxTasksHomeScreen: state.maxTasksHomeScreen ?? 4, didHydrateState, showWelcome, theme, @@ -570,6 +574,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setState((prevState) => ({ ...prevState, historyPreviewCollapsed: value })), setReasoningBlockCollapsed: (value) => setState((prevState) => ({ ...prevState, reasoningBlockCollapsed: value })), + setMaxTasksHomeScreen: (value) => setState((prevState) => ({ ...prevState, maxTasksHomeScreen: value })), setHasOpenedModeSelector: (value) => setState((prevState) => ({ ...prevState, hasOpenedModeSelector: value })), setAutoCondenseContext: (value) => setState((prevState) => ({ ...prevState, autoCondenseContext: value })), setAutoCondenseContextPercent: (value) => diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index efcd4ffa33..8e0188f68a 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -42,6 +42,10 @@ "collapseThinking": { "label": "Collapse Thinking messages by default", "description": "When enabled, thinking blocks will be collapsed by default until you interact with them" + }, + "maxTasksHomeScreen": { + "label": "Maximum number of tasks in home screen", + "description": "Number of recent tasks to show on the home screen (0-20). Set to 0 to hide the history preview entirely." } }, "prompts": {