feat: add max tasks home screen setting

- Add new UISettings option to configure maximum number of tasks shown in home screen (0-20, default 4)
- Update HistoryPreview component to respect the new setting
- When set to 0, HistoryPreview component does not render at all
- Add translation keys for the new setting
- Update tests to account for new behavior
This commit is contained in:
Roo Code 2025-11-13 15:04:40 +00:00
parent 2b26cf3011
commit bd7b31713c
7 changed files with 137 additions and 4 deletions

View file

@ -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 (
<div className="flex flex-col gap-1">
<div className="flex flex-wrap items-center justify-between mt-4 mb-2">
@ -27,7 +34,7 @@ const HistoryPreview = () => {
</div>
{tasks.length !== 0 && (
<>
{tasks.slice(0, 4).map((item) => (
{tasks.slice(0, maxTasksHomeScreen).map((item) => (
<TaskItem key={item.id} item={item} variant="compact" />
))}
</>

View file

@ -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(<HistoryPreview />)
// 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(<HistoryPreview />)
// 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()
})
})

View file

@ -201,6 +201,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
openRouterImageApiKey,
openRouterImageGenerationSelectedModel,
reasoningBlockCollapsed,
maxTasksHomeScreen,
includeCurrentTime,
includeCurrentCost,
} = cachedState
@ -393,6 +394,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ 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<SettingsViewRef, SettingsViewProps>(({ onDone, t
{activeTab === "ui" && (
<UISettings
reasoningBlockCollapsed={reasoningBlockCollapsed ?? true}
maxTasksHomeScreen={maxTasksHomeScreen ?? 4}
setCachedStateField={setCachedStateField}
/>
)}

View file

@ -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<HTMLDivElement> {
reasoningBlockCollapsed: boolean
maxTasksHomeScreen: number
setCachedStateField: SetCachedStateField<keyof ExtensionStateContextType>
}
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 (
<div {...props}>
<SectionHeader>
@ -49,6 +67,26 @@ export const UISettings = ({ reasoningBlockCollapsed, setCachedStateField, ...pr
{t("settings:ui.collapseThinking.description")}
</div>
</div>
{/* Maximum Tasks in Home Screen Setting */}
<div className="flex flex-col gap-1">
<label htmlFor="max-tasks-home-screen" className="font-medium">
{t("settings:ui.maxTasksHomeScreen.label")}
</label>
<VSCodeTextField
id="max-tasks-home-screen"
type="number"
value={maxTasksHomeScreen.toString()}
min="0"
max="20"
onChange={(e: any) => handleMaxTasksHomeScreenChange(e.target.value)}
data-testid="max-tasks-home-screen-input"
className="w-32"
/>
<div className="text-vscode-descriptionForeground text-sm mt-1">
{t("settings:ui.maxTasksHomeScreen.description")}
</div>
</div>
</div>
</Section>
</div>

View file

@ -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(<UISettings {...defaultProps} />)
const input = getByTestId("max-tasks-home-screen-input")
expect(input).toBeTruthy()
})
it("displays the correct initial state for collapse thinking", () => {
const { getByTestId } = render(<UISettings {...defaultProps} reasoningBlockCollapsed={true} />)
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(<UISettings {...defaultProps} maxTasksHomeScreen={10} />)
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(<UISettings {...defaultProps} setCachedStateField={setCachedStateField} />)
@ -40,4 +53,13 @@ describe("UISettings", () => {
rerender(<UISettings {...defaultProps} reasoningBlockCollapsed={true} />)
expect(checkbox.checked).toBe(true)
})
it("updates input value when maxTasksHomeScreen prop changes", () => {
const { getByTestId, rerender } = render(<UISettings {...defaultProps} maxTasksHomeScreen={4} />)
const input = getByTestId("max-tasks-home-screen-input") as HTMLInputElement
expect(input.value).toBe("4")
rerender(<UISettings {...defaultProps} maxTasksHomeScreen={10} />)
expect(input.value).toBe("10")
})
})

View file

@ -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) =>

View file

@ -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": {