mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
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:
parent
2b26cf3011
commit
bd7b31713c
7 changed files with 137 additions and 4 deletions
|
|
@ -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" />
|
||||
))}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue