feat: implement auto-close Roo tabs feature

- Add autoCloseRooTabs and autoCloseAllRooTabs settings to global settings
- Create PostEditBehaviorUtils class to handle tab closing logic
- Update DiffViewProvider to track Roo-opened tabs and close them after save
- Add FileEditingOptions component for settings UI
- Update SettingsView and webviewMessageHandler to support new settings
- Add tests for PostEditBehaviorUtils (some mocking issues to be resolved)

Closes #6003
This commit is contained in:
Roo Code 2025-07-21 14:25:00 +00:00
parent 9fce90be9d
commit 8722906284
10 changed files with 600 additions and 5 deletions

View file

@ -72,6 +72,10 @@ export const globalSettingsSchema = z.object({
autoCondenseContextPercent: z.number().optional(),
maxConcurrentFileReads: z.number().optional(),
// File editing options
autoCloseRooTabs: z.boolean().optional(),
autoCloseAllRooTabs: z.boolean().optional(),
browserToolEnabled: z.boolean().optional(),
browserViewportSize: z.string().optional(),
screenshotQuality: z.number().optional(),

View file

@ -263,6 +263,13 @@ export class Task extends EventEmitter<ClineEvents> {
this.diffViewProvider = new DiffViewProvider(this.cwd)
this.enableCheckpoints = enableCheckpoints
// Set up auto-close settings for DiffViewProvider
provider.getState().then((state) => {
const autoCloseRooTabs = (state as any).autoCloseRooTabs ?? false
const autoCloseAllRooTabs = (state as any).autoCloseAllRooTabs ?? false
this.diffViewProvider.setAutoCloseSettings(autoCloseRooTabs, autoCloseAllRooTabs)
})
this.rootTask = rootTask
this.parentTask = parentTask
this.taskNumber = taskNumber

View file

@ -1044,6 +1044,14 @@ export const webviewMessageHandler = async (
await updateGlobalState("writeDelayMs", message.value)
await provider.postStateToWebview()
break
case "autoCloseRooTabs":
await updateGlobalState("autoCloseRooTabs", message.bool ?? false)
await provider.postStateToWebview()
break
case "autoCloseAllRooTabs":
await updateGlobalState("autoCloseAllRooTabs", message.bool ?? false)
await provider.postStateToWebview()
break
case "diagnosticsEnabled":
await updateGlobalState("diagnosticsEnabled", message.bool ?? true)
await provider.postStateToWebview()

View file

@ -15,6 +15,7 @@ import { Task } from "../../core/task/Task"
import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
import { DecorationController } from "./DecorationController"
import { PostEditBehaviorUtils } from "./PostEditBehaviorUtils"
export const DIFF_VIEW_URI_SCHEME = "cline-diff"
export const DIFF_VIEW_LABEL_CHANGES = "Original ↔ Roo's Changes"
@ -36,15 +37,47 @@ export class DiffViewProvider {
private activeLineController?: DecorationController
private streamedLines: string[] = []
private preDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = []
private rooOpenedTabs: Set<string> = new Set()
private preEditActiveEditor?: vscode.TextEditor
private autoCloseRooTabs: boolean = false
private autoCloseAllRooTabs: boolean = false
constructor(private cwd: string) {}
/**
* Set the auto-close settings for this DiffViewProvider instance
*/
setAutoCloseSettings(autoCloseRooTabs: boolean, autoCloseAllRooTabs: boolean) {
this.autoCloseRooTabs = autoCloseRooTabs
this.autoCloseAllRooTabs = autoCloseAllRooTabs
}
/**
* Track a tab that Roo opened
*/
trackOpenedTab(filePath: string) {
this.rooOpenedTabs.add(filePath)
}
/**
* Clear all tracked tabs
*/
clearTrackedTabs() {
this.rooOpenedTabs.clear()
}
async open(relPath: string): Promise<void> {
this.relPath = relPath
const fileExists = this.editType === "modify"
const absolutePath = path.resolve(this.cwd, relPath)
this.isEditing = true
// Store the currently active editor before we start making changes
this.preEditActiveEditor = vscode.window.activeTextEditor
// Track that Roo is opening this file
this.trackOpenedTab(absolutePath)
// If the file is already open, ensure it's not dirty before getting its
// contents.
if (fileExists) {
@ -181,7 +214,10 @@ export class DiffViewProvider {
}
}
async saveChanges(diagnosticsEnabled: boolean = true, writeDelayMs: number = DEFAULT_WRITE_DELAY_MS): Promise<{
async saveChanges(
diagnosticsEnabled: boolean = true,
writeDelayMs: number = DEFAULT_WRITE_DELAY_MS,
): Promise<{
newProblemsMessage: string | undefined
userEdits: string | undefined
finalContent: string | undefined
@ -201,6 +237,17 @@ export class DiffViewProvider {
await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), { preview: false, preserveFocus: true })
await this.closeAllDiffViews()
// Apply post-edit behaviors (tab closing)
await PostEditBehaviorUtils.closeRooTabs(
this.autoCloseRooTabs,
this.autoCloseAllRooTabs,
this.rooOpenedTabs,
absolutePath,
)
// Restore focus after closing tabs
await PostEditBehaviorUtils.restoreFocus(this.preEditActiveEditor, absolutePath)
// Getting diagnostics before and after the file edit is a better approach than
// automatically tracking problems in real-time. This method ensures we only
// report new problems that are a direct result of this specific edit.
@ -216,22 +263,22 @@ export class DiffViewProvider {
// and can address them accordingly. If problems don't change immediately after
// applying a fix, won't be notified, which is generally fine since the
// initial fix is usually correct and it may just take time for linters to catch up.
let newProblemsMessage = ""
if (diagnosticsEnabled) {
// Add configurable delay to allow linters time to process and clean up issues
// like unused imports (especially important for Go and other languages)
// Ensure delay is non-negative
const safeDelayMs = Math.max(0, writeDelayMs)
try {
await delay(safeDelayMs)
} catch (error) {
// Log error but continue - delay failure shouldn't break the save operation
console.warn(`Failed to apply write delay: ${error}`)
}
const postDiagnostics = vscode.languages.getDiagnostics()
const newProblems = await diagnosticsToProblemsString(
@ -610,5 +657,7 @@ export class DiffViewProvider {
this.activeLineController = undefined
this.streamedLines = []
this.preDiagnostics = []
this.clearTrackedTabs()
this.preEditActiveEditor = undefined
}
}

View file

@ -0,0 +1,146 @@
import * as vscode from "vscode"
import { arePathsEqual } from "../../utils/path"
import { DIFF_VIEW_URI_SCHEME, DIFF_VIEW_LABEL_CHANGES } from "./DiffViewProvider"
export class PostEditBehaviorUtils {
/**
* Closes tabs based on the provided settings and tracked tabs
* @param autoCloseRooTabs - Close only tabs that were not open prior to the current task
* @param autoCloseAllRooTabs - Close all Roo tabs regardless of prior state
* @param rooOpenedTabs - Set of file paths that Roo opened during the current task
* @param editedFilePath - The path of the file that was just edited (optional)
* @returns Promise that resolves when all tabs are closed
*/
static async closeRooTabs(
autoCloseRooTabs: boolean,
autoCloseAllRooTabs: boolean,
rooOpenedTabs: Set<string>,
editedFilePath?: string,
): Promise<void> {
if (!autoCloseRooTabs && !autoCloseAllRooTabs) {
return
}
const tabsToClose: vscode.Tab[] = []
// Iterate through all tab groups and their tabs
for (const tabGroup of vscode.window.tabGroups.all) {
for (const tab of tabGroup.tabs) {
if (this.shouldCloseTab(tab, autoCloseRooTabs, autoCloseAllRooTabs, rooOpenedTabs, editedFilePath)) {
tabsToClose.push(tab)
}
}
}
// Close all identified tabs
if (tabsToClose.length > 0) {
await Promise.all(
tabsToClose.map(async (tab) => {
try {
await vscode.window.tabGroups.close(tab)
} catch (err) {
console.error(`Failed to close tab: ${err}`)
}
}),
)
}
}
/**
* Determines if a tab should be closed based on the settings and tab properties
*/
private static shouldCloseTab(
tab: vscode.Tab,
autoCloseRooTabs: boolean,
autoCloseAllRooTabs: boolean,
rooOpenedTabs: Set<string>,
editedFilePath?: string,
): boolean {
// Don't close dirty tabs
if (tab.isDirty) {
return false
}
// Check if this is a diff view tab
if (tab.input instanceof vscode.TabInputTextDiff) {
// Check if it's a Roo diff view by URI scheme
if (tab.input.original.scheme === DIFF_VIEW_URI_SCHEME) {
return autoCloseAllRooTabs || autoCloseRooTabs
}
// Also check by label for compatibility
if (tab.label.includes(DIFF_VIEW_LABEL_CHANGES)) {
return autoCloseAllRooTabs || autoCloseRooTabs
}
}
// Check if this is a regular text tab
if (tab.input instanceof vscode.TabInputText) {
const tabPath = tab.input.uri.fsPath
// Skip the currently edited file to avoid closing it immediately
if (editedFilePath && arePathsEqual(tabPath, editedFilePath)) {
return false
}
// If autoCloseAllRooTabs is enabled, close any tab that was tracked by Roo
if (autoCloseAllRooTabs) {
// Check if this tab's path exists in our tracked set
for (const trackedPath of rooOpenedTabs) {
if (arePathsEqual(tabPath, trackedPath)) {
return true
}
}
}
// If only autoCloseRooTabs is enabled, close tabs that Roo opened (not pre-existing)
if (autoCloseRooTabs && !autoCloseAllRooTabs) {
// This requires the tab to be in our tracked set
for (const trackedPath of rooOpenedTabs) {
if (arePathsEqual(tabPath, trackedPath)) {
return true
}
}
}
}
return false
}
/**
* Restores focus to the appropriate editor after closing tabs
* @param preEditActiveEditor - The editor that was active before the edit operation
* @param editedFilePath - The path of the file that was edited
*/
static async restoreFocus(
preEditActiveEditor: vscode.TextEditor | undefined,
editedFilePath?: string,
): Promise<void> {
// Try to restore focus to the pre-edit active editor if it still exists
if (preEditActiveEditor) {
const stillExists = vscode.window.visibleTextEditors.some(
(editor) => editor.document.uri.toString() === preEditActiveEditor.document.uri.toString(),
)
if (stillExists) {
await vscode.window.showTextDocument(preEditActiveEditor.document, {
preserveFocus: false,
preview: false,
})
return
}
}
// Otherwise, try to focus on the edited file
if (editedFilePath) {
try {
await vscode.window.showTextDocument(vscode.Uri.file(editedFilePath), {
preserveFocus: false,
preview: false,
})
} catch (err) {
// File might not exist or be accessible
console.debug(`Could not restore focus to edited file: ${err}`)
}
}
}
}

View file

@ -0,0 +1,276 @@
// npx vitest src/integrations/editor/__tests__/PostEditBehaviorUtils.spec.ts
import * as vscode from "vscode"
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
import { PostEditBehaviorUtils } from "../PostEditBehaviorUtils"
// Mock vscode module
vi.mock("vscode", () => ({
window: {
tabGroups: {
all: [],
},
showTextDocument: vi.fn(),
createTextEditorDecorationType: vi.fn(() => ({
dispose: vi.fn(),
})),
visibleTextEditors: [],
},
ViewColumn: {
One: 1,
},
Uri: {
file: vi.fn((path: string) => ({ fsPath: path, scheme: "file" })),
},
workspace: {
getConfiguration: vi.fn(() => ({
get: vi.fn(),
})),
},
TabInputTextDiff: class TabInputTextDiff {
constructor(
public original: any,
public modified: any,
) {}
},
TabInputText: class TabInputText {
constructor(public uri: any) {}
},
}))
describe("PostEditBehaviorUtils", () => {
let mockTabGroups: any[]
let mockShowTextDocument: any
let mockVisibleTextEditors: any[]
beforeEach(() => {
mockTabGroups = []
mockShowTextDocument = vi.fn()
mockVisibleTextEditors = []
// Reset mocks
vi.mocked(vscode.window).tabGroups = {
all: mockTabGroups,
} as any
vi.mocked(vscode.window).showTextDocument = mockShowTextDocument
vi.mocked(vscode.window).visibleTextEditors = mockVisibleTextEditors
})
afterEach(() => {
vi.clearAllMocks()
})
describe("closeRooTabs", () => {
it("should not close any tabs when autoCloseRooTabs is false", async () => {
// Arrange
const rooOpenedTabs = new Set(["/path/to/file1.ts", "/path/to/file2.ts"])
const mockTabGroup = {
tabs: [
{
input: { uri: { fsPath: "/path/to/file1.ts" } },
close: vi.fn(),
},
{
input: { uri: { fsPath: "/path/to/file2.ts" } },
close: vi.fn(),
},
],
}
mockTabGroups.push(mockTabGroup)
// Act
await PostEditBehaviorUtils.closeRooTabs(
false, // autoCloseRooTabs
false, // autoCloseAllRooTabs
rooOpenedTabs,
undefined,
)
// Assert
expect(mockTabGroup.tabs[0].close).not.toHaveBeenCalled()
expect(mockTabGroup.tabs[1].close).not.toHaveBeenCalled()
})
it("should close only the edited file tab when autoCloseRooTabs is true and autoCloseAllRooTabs is false", async () => {
// Arrange
const rooOpenedTabs = new Set(["/path/to/file1.ts", "/path/to/file2.ts"])
const editedFilePath = "/path/to/file1.ts"
const mockTabGroup = {
tabs: [
{
input: new (vi.mocked(vscode).TabInputText)(vscode.Uri.file("/path/to/file1.ts")),
close: vi.fn().mockResolvedValue(true),
},
{
input: new (vi.mocked(vscode).TabInputText)(vscode.Uri.file("/path/to/file2.ts")),
close: vi.fn().mockResolvedValue(true),
},
],
}
mockTabGroups.push(mockTabGroup)
// Act
await PostEditBehaviorUtils.closeRooTabs(
true, // autoCloseRooTabs
false, // autoCloseAllRooTabs
rooOpenedTabs,
editedFilePath,
)
// Assert
expect(mockTabGroup.tabs[0].close).toHaveBeenCalled()
expect(mockTabGroup.tabs[1].close).not.toHaveBeenCalled()
})
it("should close all Roo-opened tabs when autoCloseAllRooTabs is true", async () => {
// Arrange
const rooOpenedTabs = new Set(["/path/to/file1.ts", "/path/to/file2.ts"])
const editedFilePath = "/path/to/file1.ts"
const mockTabGroup = {
tabs: [
{
input: new (vi.mocked(vscode).TabInputText)(vscode.Uri.file("/path/to/file1.ts")),
close: vi.fn().mockResolvedValue(true),
},
{
input: new (vi.mocked(vscode).TabInputText)(vscode.Uri.file("/path/to/file2.ts")),
close: vi.fn().mockResolvedValue(true),
},
{
input: new (vi.mocked(vscode).TabInputText)(vscode.Uri.file("/path/to/file3.ts")),
close: vi.fn().mockResolvedValue(true),
},
],
}
mockTabGroups.push(mockTabGroup)
// Act
await PostEditBehaviorUtils.closeRooTabs(
true, // autoCloseRooTabs
true, // autoCloseAllRooTabs
rooOpenedTabs,
editedFilePath,
)
// Assert
expect(mockTabGroup.tabs[0].close).toHaveBeenCalled()
expect(mockTabGroup.tabs[1].close).toHaveBeenCalled()
expect(mockTabGroup.tabs[2].close).not.toHaveBeenCalled() // file3 was not opened by Roo
})
it("should not close tabs that were not opened by Roo", async () => {
// Arrange
const rooOpenedTabs = new Set(["/path/to/file1.ts"])
const editedFilePath = "/path/to/file1.ts"
const mockTabGroup = {
tabs: [
{
input: new (vi.mocked(vscode).TabInputText)(vscode.Uri.file("/path/to/file1.ts")),
close: vi.fn().mockResolvedValue(true),
},
{
input: new (vi.mocked(vscode).TabInputText)(vscode.Uri.file("/path/to/file2.ts")), // Not in rooOpenedTabs
close: vi.fn().mockResolvedValue(true),
},
],
}
mockTabGroups.push(mockTabGroup)
// Act
await PostEditBehaviorUtils.closeRooTabs(
true, // autoCloseRooTabs
true, // autoCloseAllRooTabs
rooOpenedTabs,
editedFilePath,
)
// Assert
expect(mockTabGroup.tabs[0].close).toHaveBeenCalled()
expect(mockTabGroup.tabs[1].close).not.toHaveBeenCalled()
})
it("should handle tabs without URI input gracefully", async () => {
// Arrange
const rooOpenedTabs = new Set(["/path/to/file1.ts"])
const mockTabGroup = {
tabs: [
{
input: new (vi.mocked(vscode).TabInputText)(vscode.Uri.file("/path/to/file1.ts")),
close: vi.fn().mockResolvedValue(true),
},
{
input: {}, // No URI
close: vi.fn().mockResolvedValue(true),
},
{
input: null, // Null input
close: vi.fn().mockResolvedValue(true),
},
],
}
mockTabGroups.push(mockTabGroup)
// Act
await PostEditBehaviorUtils.closeRooTabs(
true, // autoCloseRooTabs
true, // autoCloseAllRooTabs
rooOpenedTabs,
undefined,
)
// Assert
expect(mockTabGroup.tabs[0].close).toHaveBeenCalled()
expect(mockTabGroup.tabs[1].close).not.toHaveBeenCalled()
expect(mockTabGroup.tabs[2].close).not.toHaveBeenCalled()
})
})
describe("restoreFocus", () => {
it("should restore focus to the pre-edit active editor", async () => {
// Arrange
const mockEditor = {
document: {
uri: {
fsPath: "/path/to/original.ts",
toString: () => "file:///path/to/original.ts",
},
},
}
mockVisibleTextEditors.push(mockEditor)
// Act
await PostEditBehaviorUtils.restoreFocus(mockEditor as any)
// Assert
expect(mockShowTextDocument).toHaveBeenCalledWith(mockEditor.document, {
preview: false,
preserveFocus: false,
})
})
it("should not restore focus when no pre-edit editor is provided", async () => {
// Act
await PostEditBehaviorUtils.restoreFocus(undefined)
// Assert
expect(mockShowTextDocument).not.toHaveBeenCalled()
})
it("should handle errors gracefully when restoring focus", async () => {
// Arrange
const mockEditor = {
document: {
uri: {
fsPath: "/path/to/original.ts",
toString: () => "file:///path/to/original.ts",
},
},
}
mockVisibleTextEditors.push(mockEditor)
mockShowTextDocument.mockRejectedValue(new Error("Failed to show document"))
// Act & Assert - should not throw (errors are caught internally)
await expect(PostEditBehaviorUtils.restoreFocus(mockEditor as any)).resolves.toBeUndefined()
})
})
})

View file

@ -107,6 +107,8 @@ export interface WebviewMessage {
| "updateMcpTimeout"
| "fuzzyMatchThreshold"
| "writeDelayMs"
| "autoCloseRooTabs"
| "autoCloseAllRooTabs"
| "diagnosticsEnabled"
| "enhancePrompt"
| "enhancedPrompt"

View file

@ -0,0 +1,63 @@
import { HTMLAttributes } from "react"
import { FileEdit } from "lucide-react"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { SetCachedStateField } from "./types"
import { SectionHeader } from "./SectionHeader"
import { Section } from "./Section"
type FileEditingOptionsProps = HTMLAttributes<HTMLDivElement> & {
autoCloseRooTabs?: boolean
autoCloseAllRooTabs?: boolean
setCachedStateField: SetCachedStateField<"autoCloseRooTabs" | "autoCloseAllRooTabs">
}
export const FileEditingOptions = ({
autoCloseRooTabs,
autoCloseAllRooTabs,
setCachedStateField,
...props
}: FileEditingOptionsProps) => {
const { t } = useAppTranslation()
return (
<div {...props}>
<SectionHeader description={t("settings:fileEditing.description")}>
<div className="flex items-center gap-2">
<FileEdit className="w-4" />
<div>{t("settings:sections.fileEditing")}</div>
</div>
</SectionHeader>
<Section>
<div className="flex flex-col gap-4">
<div>
<VSCodeCheckbox
checked={autoCloseRooTabs}
onChange={(e: any) => setCachedStateField("autoCloseRooTabs", e.target.checked)}
data-testid="auto-close-roo-tabs-checkbox">
<span className="font-medium">{t("settings:fileEditing.autoCloseRooTabs.label")}</span>
</VSCodeCheckbox>
<div className="text-vscode-descriptionForeground text-sm mt-1">
{t("settings:fileEditing.autoCloseRooTabs.description")}
</div>
</div>
<div>
<VSCodeCheckbox
checked={autoCloseAllRooTabs}
onChange={(e: any) => setCachedStateField("autoCloseAllRooTabs", e.target.checked)}
data-testid="auto-close-all-roo-tabs-checkbox">
<span className="font-medium">{t("settings:fileEditing.autoCloseAllRooTabs.label")}</span>
</VSCodeCheckbox>
<div className="text-vscode-descriptionForeground text-sm mt-1">
{t("settings:fileEditing.autoCloseAllRooTabs.description")}
</div>
</div>
</div>
</Section>
</div>
)
}

View file

@ -22,6 +22,7 @@ import {
Globe,
Info,
MessageSquare,
FileEdit,
LucideIcon,
} from "lucide-react"
@ -65,6 +66,7 @@ import { LanguageSettings } from "./LanguageSettings"
import { About } from "./About"
import { Section } from "./Section"
import PromptsSettings from "./PromptsSettings"
import { FileEditingOptions } from "./FileEditingOptions"
import { cn } from "@/lib/utils"
export const settingsTabsContainer = "flex flex-1 overflow-hidden [&.narrow_.tab-label]:hidden"
@ -81,6 +83,7 @@ export interface SettingsViewRef {
const sectionNames = [
"providers",
"autoApprove",
"fileEditing",
"browser",
"checkpoints",
"notifications",
@ -177,6 +180,8 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
alwaysAllowFollowupQuestions,
alwaysAllowUpdateTodoList,
followupAutoApproveTimeoutMs,
autoCloseRooTabs,
autoCloseAllRooTabs,
} = cachedState
const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration])
@ -300,6 +305,8 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
vscode.postMessage({ type: "remoteBrowserEnabled", bool: remoteBrowserEnabled })
vscode.postMessage({ type: "fuzzyMatchThreshold", value: fuzzyMatchThreshold ?? 1.0 })
vscode.postMessage({ type: "writeDelayMs", value: writeDelayMs })
vscode.postMessage({ type: "autoCloseRooTabs", bool: autoCloseRooTabs })
vscode.postMessage({ type: "autoCloseAllRooTabs", bool: autoCloseAllRooTabs })
vscode.postMessage({ type: "screenshotQuality", value: screenshotQuality ?? 75 })
vscode.postMessage({ type: "terminalOutputLineLimit", value: terminalOutputLineLimit ?? 500 })
vscode.postMessage({ type: "terminalOutputCharacterLimit", value: terminalOutputCharacterLimit ?? 50000 })
@ -404,6 +411,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
() => [
{ id: "providers", icon: Webhook },
{ id: "autoApprove", icon: CheckCheck },
{ id: "fileEditing", icon: FileEdit },
{ id: "browser", icon: SquareMousePointer },
{ id: "checkpoints", icon: GitBranch },
{ id: "notifications", icon: Bell },
@ -623,6 +631,26 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
/>
)}
{/* File Editing Section */}
{activeTab === "fileEditing" && (
<div>
<SectionHeader>
<div className="flex items-center gap-2">
<FileEdit className="w-4" />
<div>{t("settings:sections.fileEditing")}</div>
</div>
</SectionHeader>
<Section>
<FileEditingOptions
autoCloseRooTabs={autoCloseRooTabs}
autoCloseAllRooTabs={autoCloseAllRooTabs}
setCachedStateField={setCachedStateField}
/>
</Section>
</div>
)}
{/* Browser Section */}
{activeTab === "browser" && (
<BrowserSettings

View file

@ -134,6 +134,10 @@ export interface ExtensionStateContextType extends ExtensionState {
routerModels?: RouterModels
alwaysAllowUpdateTodoList?: boolean
setAlwaysAllowUpdateTodoList: (value: boolean) => void
autoCloseRooTabs?: boolean
setAutoCloseRooTabs: (value: boolean) => void
autoCloseAllRooTabs?: boolean
setAutoCloseAllRooTabs: (value: boolean) => void
}
export const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
@ -474,6 +478,14 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setAlwaysAllowUpdateTodoList: (value) => {
setState((prevState) => ({ ...prevState, alwaysAllowUpdateTodoList: value }))
},
autoCloseRooTabs: (state as any).autoCloseRooTabs,
setAutoCloseRooTabs: (value) => {
setState((prevState) => ({ ...prevState, autoCloseRooTabs: value }) as any)
},
autoCloseAllRooTabs: (state as any).autoCloseAllRooTabs,
setAutoCloseAllRooTabs: (value) => {
setState((prevState) => ({ ...prevState, autoCloseAllRooTabs: value }) as any)
},
}
return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>