Adds maxTasksHomeScreen UI setting

This commit is contained in:
Bruno Bergher 2025-11-13 15:41:54 +00:00
parent 47e3b606f2
commit acdf4febe0
6 changed files with 164 additions and 33 deletions

View file

@ -0,0 +1,99 @@
import { describe, it, expect, beforeEach, vi } from "vitest"
import * as vscode from "vscode"
import { ContextProxy } from "../ContextProxy"
describe("maxTasksHomeScreen setting", () => {
let mockContext: vscode.ExtensionContext
let contextProxy: ContextProxy
beforeEach(async () => {
// Create mock VSCode context
const mockGlobalState = new Map<string, any>()
const mockSecrets = new Map<string, string>()
mockContext = {
globalState: {
get: vi.fn((key: string) => mockGlobalState.get(key)),
update: vi.fn(async (key: string, value: any) => {
mockGlobalState.set(key, value)
}),
keys: vi.fn(() => Array.from(mockGlobalState.keys())),
setKeysForSync: vi.fn(),
},
secrets: {
get: vi.fn(async (key: string) => mockSecrets.get(key)),
store: vi.fn(async (key: string, value: string) => {
mockSecrets.set(key, value)
}),
delete: vi.fn(async (key: string) => {
mockSecrets.delete(key)
}),
onDidChange: vi.fn(),
},
extensionUri: {} as vscode.Uri,
extensionPath: "/test/path",
globalStorageUri: {} as vscode.Uri,
logUri: {} as vscode.Uri,
extension: {} as vscode.Extension<any>,
extensionMode: 3, // vscode.ExtensionMode.Test
} as unknown as vscode.ExtensionContext
contextProxy = new ContextProxy(mockContext)
await contextProxy.initialize()
})
it("should save maxTasksHomeScreen value", async () => {
// Set the value
await contextProxy.setValue("maxTasksHomeScreen", 10)
// Verify it was saved
expect(mockContext.globalState.update).toHaveBeenCalledWith("maxTasksHomeScreen", 10)
})
it("should retrieve maxTasksHomeScreen value", async () => {
// Set the value
await contextProxy.setValue("maxTasksHomeScreen", 15)
// Get the value
const value = contextProxy.getValue("maxTasksHomeScreen")
// Verify it matches
expect(value).toBe(15)
})
it("should persist maxTasksHomeScreen across initialization", async () => {
// Set the value
await contextProxy.setValue("maxTasksHomeScreen", 8)
// Create a new instance (simulating restart)
const newContextProxy = new ContextProxy(mockContext)
await newContextProxy.initialize()
// Get the value from the new instance
const value = newContextProxy.getValue("maxTasksHomeScreen")
// Verify it was persisted
expect(value).toBe(8)
})
it("should handle default value of 4", async () => {
// Don't set any value, should use default
const values = contextProxy.getValues()
// maxTasksHomeScreen should be undefined or 4 (depending on implementation)
expect(values.maxTasksHomeScreen === undefined || values.maxTasksHomeScreen === 4).toBe(true)
})
it("should validate min/max bounds", async () => {
// The schema should enforce min=0, max=20
// Try setting valid values
await contextProxy.setValue("maxTasksHomeScreen", 0)
expect(contextProxy.getValue("maxTasksHomeScreen")).toBe(0)
await contextProxy.setValue("maxTasksHomeScreen", 20)
expect(contextProxy.getValue("maxTasksHomeScreen")).toBe(20)
await contextProxy.setValue("maxTasksHomeScreen", 10)
expect(contextProxy.getValue("maxTasksHomeScreen")).toBe(10)
})
})

View file

@ -1916,6 +1916,7 @@ export class ClineProvider
includeDiagnosticMessages,
maxDiagnosticMessages,
includeTaskHistoryInEnhance,
maxTasksHomeScreen,
includeCurrentTime,
includeCurrentCost,
taskSyncEnabled,
@ -2080,6 +2081,7 @@ export class ClineProvider
includeDiagnosticMessages: includeDiagnosticMessages ?? true,
maxDiagnosticMessages: maxDiagnosticMessages ?? 50,
includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? true,
maxTasksHomeScreen: maxTasksHomeScreen ?? 4,
includeCurrentTime: includeCurrentTime ?? true,
includeCurrentCost: includeCurrentCost ?? true,
taskSyncEnabled,
@ -2295,6 +2297,7 @@ export class ClineProvider
includeDiagnosticMessages: stateValues.includeDiagnosticMessages ?? true,
maxDiagnosticMessages: stateValues.maxDiagnosticMessages ?? 50,
includeTaskHistoryInEnhance: stateValues.includeTaskHistoryInEnhance ?? true,
maxTasksHomeScreen: stateValues.maxTasksHomeScreen ?? 4,
includeCurrentTime: stateValues.includeCurrentTime ?? true,
includeCurrentCost: stateValues.includeCurrentCost ?? true,
taskSyncEnabled,

View file

@ -0,0 +1,28 @@
import { describe, it, expect } from "vitest"
import type { ExtensionState } from "../../../shared/ExtensionMessage"
/**
* Integration test for maxTasksHomeScreen setting
* This test verifies that the setting is properly typed in ExtensionState
*/
describe("maxTasksHomeScreen integration", () => {
it("should be a valid ExtensionState property", () => {
// Type-level test: This will fail to compile if maxTasksHomeScreen is not in ExtensionState
const state: Partial<ExtensionState> = {
maxTasksHomeScreen: 10,
}
expect(state.maxTasksHomeScreen).toBe(10)
})
it("should accept valid range values", () => {
const validValues = [0, 4, 10, 15, 20]
validValues.forEach((value) => {
const state: Partial<ExtensionState> = {
maxTasksHomeScreen: value,
}
expect(state.maxTasksHomeScreen).toBe(value)
})
})
})

View file

@ -1,9 +1,11 @@
import { HTMLAttributes } from "react"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { VSCodeCheckbox, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { Glasses } from "lucide-react"
import { telemetryClient } from "@/utils/TelemetryClient"
import { Slider } from "@/components/ui"
import { SetCachedStateField } from "./types"
import { SectionHeader } from "./SectionHeader"
import { Section } from "./Section"
@ -32,16 +34,13 @@ export const UISettings = ({
})
}
const handleMaxTasksHomeScreenChange = (value: string) => {
const numValue = parseInt(value, 10)
if (!isNaN(numValue) && numValue >= 0 && numValue <= 20) {
setCachedStateField("maxTasksHomeScreen", numValue)
const handleMaxTasksHomeScreenChange = (value: number) => {
setCachedStateField("maxTasksHomeScreen", value)
// Track telemetry event
telemetryClient.capture("ui_settings_max_tasks_home_screen_changed", {
value: numValue,
})
}
// Track telemetry event
telemetryClient.capture("ui_settings_max_tasks_home_screen_changed", {
value: value,
})
}
return (
@ -70,18 +69,17 @@ export const UISettings = ({
{/* 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"
value={maxTasksHomeScreen.toString()}
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")}
<label className="block font-medium mb-1">{t("settings:ui.maxTasksHomeScreen.label")}</label>
<div className="flex items-center gap-2">
<Slider
min={0}
max={20}
step={1}
value={[maxTasksHomeScreen]}
onValueChange={([value]) => handleMaxTasksHomeScreenChange(value)}
data-testid="max-tasks-home-screen-slider"
/>
<span className="w-10">{maxTasksHomeScreen}</span>
</div>
</div>
</div>

View file

@ -15,10 +15,10 @@ describe("UISettings", () => {
expect(checkbox).toBeTruthy()
})
it("renders the max tasks home screen input", () => {
it("renders the max tasks home screen slider", () => {
const { getByTestId } = render(<UISettings {...defaultProps} />)
const input = getByTestId("max-tasks-home-screen-input")
expect(input).toBeTruthy()
const slider = getByTestId("max-tasks-home-screen-slider")
expect(slider).toBeTruthy()
})
it("displays the correct initial state for collapse thinking", () => {
@ -29,8 +29,9 @@ describe("UISettings", () => {
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")
const slider = getByTestId("max-tasks-home-screen-slider")
const thumb = slider.querySelector('[role="slider"]') as HTMLElement
expect(thumb.getAttribute("aria-valuenow")).toBe("10")
})
it("calls setCachedStateField when checkbox is toggled", async () => {
@ -54,12 +55,15 @@ describe("UISettings", () => {
expect(checkbox.checked).toBe(true)
})
it("updates input value when maxTasksHomeScreen prop changes", () => {
it("updates slider 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")
let slider = getByTestId("max-tasks-home-screen-slider")
let thumb = slider.querySelector('[role="slider"]') as HTMLElement
expect(thumb.getAttribute("aria-valuenow")).toBe("4")
rerender(<UISettings {...defaultProps} maxTasksHomeScreen={10} />)
expect(input.value).toBe("10")
slider = getByTestId("max-tasks-home-screen-slider")
thumb = slider.querySelector('[role="slider"]') as HTMLElement
expect(thumb.getAttribute("aria-valuenow")).toBe("10")
})
})

View file

@ -44,8 +44,7 @@
"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."
"label": "Maximum number of tasks in home screen"
}
},
"prompts": {