Revert "feat(settings): replace task-icon toggle with Tasks-button toggle, expose in View menu (#1271)"

This reverts commit a02987e604.
This commit is contained in:
Jinwoo-H
2026-04-30 17:12:05 -04:00
parent fcb8a21a24
commit 0bfe22ce1d
15 changed files with 82 additions and 310 deletions
@@ -64,7 +64,7 @@ function createSettings(overrides: Partial<GlobalSettings> = {}): GlobalSettings
openLinksInApp: false,
rightSidebarOpenByDefault: true,
showTitlebarAgentActivity: true,
showTasksButton: true,
showTaskProviderIcons: true,
diffDefaultView: 'inline',
notifications: {
enabled: true,
+1 -1
View File
@@ -58,7 +58,7 @@ function createSettings(overrides: Partial<GlobalSettings> = {}): GlobalSettings
openLinksInApp: false,
rightSidebarOpenByDefault: true,
showTitlebarAgentActivity: true,
showTasksButton: true,
showTaskProviderIcons: true,
diffDefaultView: 'inline',
notifications: {
enabled: true,
+3 -35
View File
@@ -18,7 +18,7 @@ import { registerCoreHandlers } from './ipc/register-core-handlers'
import { triggerStartupNotificationRegistration } from './ipc/notifications'
import { OrcaRuntimeService } from './runtime/orca-runtime'
import { OrcaRuntimeRpcServer } from './runtime/runtime-rpc'
import { registerAppMenu, rebuildAppMenu } from './menu/register-app-menu'
import { registerAppMenu } from './menu/register-app-menu'
import { checkForUpdatesFromMenu, isQuittingForUpdate } from './updater'
import {
configureDevUserDataPath,
@@ -396,40 +396,8 @@ app.whenReady().then(async () => {
onZoomReset: () => {
mainWindow?.webContents.send('terminal:zoom', 'reset')
},
onToggleLeftSidebar: () => {
mainWindow?.webContents.send('ui:toggleLeftSidebar')
},
onToggleRightSidebar: () => {
mainWindow?.webContents.send('ui:toggleRightSidebar')
},
onToggleAppearance: (key) => {
if (!store) {
return
}
if (key === 'statusBarVisible') {
// Why: status bar visibility lives under the persisted UI state
// (ui:set/ui:get), not settings. The renderer owns the authoritative
// toggle logic (it knows the current value and persists it back), so
// we forward the event and let it flip + store.
mainWindow?.webContents.send('ui:toggleStatusBar')
return
}
const current = store.getSettings()
store.updateSettings({ [key]: !current[key] })
// Why: settings:get returns the current snapshot; renderer tracks
// settings through window.api.settings.get(). Push the new value so
// the sidebar/titlebar re-render without waiting for a round-trip.
mainWindow?.webContents.send('settings:changed', { [key]: !current[key] })
rebuildAppMenu()
},
getAppearanceState: () => {
const settings = store?.getSettings()
const ui = store?.getUI()
return {
showTasksButton: settings?.showTasksButton !== false,
showTitlebarAgentActivity: settings?.showTitlebarAgentActivity !== false,
statusBarVisible: ui?.statusBarVisible !== false
}
onToggleStatusBar: () => {
mainWindow?.webContents.send('ui:toggleStatusBar')
}
})
runtimeRpc = new OrcaRuntimeRpcServer({
+1 -15
View File
@@ -3,16 +3,6 @@ import type { Store } from '../persistence'
import type { GlobalSettings, PersistedState } from '../../shared/types'
import { listSystemFontFamilies } from '../system-fonts'
import { previewGhosttyImport } from '../ghostty/index'
import { rebuildAppMenu } from '../menu/register-app-menu'
// Why: fields that appear in the View > Appearance submenu need the menu
// rebuilt after any update so the checkbox `checked` state stays in sync
// with the persisted value. Electron doesn't reactively re-render menu
// items when the backing state changes.
const APPEARANCE_MENU_KEYS: readonly (keyof GlobalSettings)[] = [
'showTasksButton',
'showTitlebarAgentActivity'
]
export function registerSettingsHandlers(store: Store): void {
ipcMain.handle('settings:get', () => {
@@ -23,11 +13,7 @@ export function registerSettingsHandlers(store: Store): void {
if (args.theme) {
nativeTheme.themeSource = args.theme
}
const result = store.updateSettings(args)
if (APPEARANCE_MENU_KEYS.some((key) => key in args)) {
rebuildAppMenu()
}
return result
return store.updateSettings(args)
})
ipcMain.handle('settings:listFonts', () => {
+1 -78
View File
@@ -30,14 +30,7 @@ function buildMenuOptions() {
onZoomIn: vi.fn(),
onZoomOut: vi.fn(),
onZoomReset: vi.fn(),
onToggleLeftSidebar: vi.fn(),
onToggleRightSidebar: vi.fn(),
onToggleAppearance: vi.fn(),
getAppearanceState: vi.fn(() => ({
showTasksButton: true,
showTitlebarAgentActivity: true,
statusBarVisible: true
}))
onToggleStatusBar: vi.fn()
}
}
@@ -189,74 +182,4 @@ describe('registerAppMenu', () => {
// No Help menu on macOS — About/Check for Updates live in the app menu.
expect(template.find((item) => item.label === 'Help')).toBeUndefined()
})
it('exposes an Appearance submenu under View with checkbox items reflecting state', () => {
const options = buildMenuOptions()
options.getAppearanceState.mockReturnValue({
showTasksButton: false,
showTitlebarAgentActivity: true,
statusBarVisible: true
})
registerAppMenu(options)
const viewSubmenu = getSubmenu(getTemplate(), 'View')
const appearanceEntry = viewSubmenu.find((item) => item.label === 'Appearance')
expect(appearanceEntry).toBeDefined()
const appearanceSubmenu = (appearanceEntry?.submenu ??
[]) as Electron.MenuItemConstructorOptions[]
const tasksItem = appearanceSubmenu.find((item) => item.label === 'Show Tasks Button')
expect(tasksItem?.type).toBe('checkbox')
expect(tasksItem?.checked).toBe(false)
const titlebarItem = appearanceSubmenu.find(
(item) => item.label === 'Show Titlebar Agent Activity'
)
expect(titlebarItem?.checked).toBe(true)
const statusBarItem = appearanceSubmenu.find((item) => item.label === 'Show Status Bar')
expect(statusBarItem?.checked).toBe(true)
})
it('routes Appearance checkbox clicks through onToggleAppearance', () => {
const options = buildMenuOptions()
registerAppMenu(options)
const viewSubmenu = getSubmenu(getTemplate(), 'View')
const appearanceSubmenu = (viewSubmenu.find((item) => item.label === 'Appearance')?.submenu ??
[]) as Electron.MenuItemConstructorOptions[]
appearanceSubmenu
.find((item) => item.label === 'Show Tasks Button')
?.click?.({} as never, {} as never, {} as never)
expect(options.onToggleAppearance).toHaveBeenCalledWith('showTasksButton')
})
it('routes sidebar toggle items through their callbacks', () => {
const options = buildMenuOptions()
registerAppMenu(options)
const viewSubmenu = getSubmenu(getTemplate(), 'View')
const appearanceSubmenu = (viewSubmenu.find((item) => item.label === 'Appearance')?.submenu ??
[]) as Electron.MenuItemConstructorOptions[]
const leftLabel = `Toggle Left Sidebar\t${isMac ? 'Cmd+B' : 'Ctrl+B'}`
const rightLabel = `Toggle Right Sidebar\t${isMac ? 'Alt+Cmd+B' : 'Ctrl+Alt+B'}`
appearanceSubmenu
.find((item) => item.label === leftLabel)
?.click?.({} as never, {} as never, {} as never)
appearanceSubmenu
.find((item) => item.label === rightLabel)
?.click?.({} as never, {} as never, {} as never)
expect(options.onToggleLeftSidebar).toHaveBeenCalledTimes(1)
expect(options.onToggleRightSidebar).toHaveBeenCalledTimes(1)
// Why: these entries must not bind Cmd/Ctrl+B as real accelerators
// because before-input-event carries a TipTap-bold carve-out that the
// menu accelerator would bypass.
expect(appearanceSubmenu.find((item) => item.label === leftLabel)?.accelerator).toBeUndefined()
expect(appearanceSubmenu.find((item) => item.label === rightLabel)?.accelerator).toBeUndefined()
})
})
+13 -91
View File
@@ -1,40 +1,23 @@
import { BrowserWindow, Menu, app } from 'electron'
export type AppearanceMenuState = {
showTasksButton: boolean
showTitlebarAgentActivity: boolean
statusBarVisible: boolean
}
export type AppearanceMenuKey = keyof AppearanceMenuState
type RegisterAppMenuOptions = {
onOpenSettings: () => void
onCheckForUpdates: (options: { includePrerelease: boolean }) => void
onZoomIn: () => void
onZoomOut: () => void
onZoomReset: () => void
onToggleLeftSidebar: () => void
onToggleRightSidebar: () => void
onToggleAppearance: (key: AppearanceMenuKey) => void
getAppearanceState: () => AppearanceMenuState
onToggleStatusBar: () => void
}
function buildAndApplyMenu(options: RegisterAppMenuOptions): void {
const {
onOpenSettings,
onCheckForUpdates,
onZoomIn,
onZoomOut,
onZoomReset,
onToggleLeftSidebar,
onToggleRightSidebar,
onToggleAppearance,
getAppearanceState
} = options
export function registerAppMenu({
onOpenSettings,
onCheckForUpdates,
onZoomIn,
onZoomOut,
onZoomReset,
onToggleStatusBar
}: RegisterAppMenuOptions): void {
const isMac = process.platform === 'darwin'
const appearance = getAppearanceState()
const reloadFocusedWindow = (ignoreCache: boolean): void => {
const webContents = BrowserWindow.getFocusedWindow()?.webContents
@@ -142,53 +125,6 @@ function buildAndApplyMenu(options: RegisterAppMenuOptions): void {
]
}
// Why: mirror VS Code's View > Appearance submenu so users can toggle
// sidebar/status-bar/tasks-button/titlebar-activity from the menu bar as
// well as from the settings pane. Electron doesn't reactively update
// menu items when the backing state changes, so rebuildAppMenu() must be
// called after every settings update — each build reads current
// appearance state through getAppearanceState() and produces a fresh
// template with accurate `checked` values.
const appearanceSubmenu: Electron.MenuItemConstructorOptions = {
label: 'Appearance',
submenu: [
{
// Why: display-only shortcut hint — not a real accelerator. Cmd/Ctrl+B
// is intercepted in createMainWindow.ts's before-input-event handler
// with a TipTap-bold carve-out for markdown editors. Binding the
// accelerator here would steal the chord before that carve-out can
// fire. Sidebar open/closed lives in the renderer store (non-persisted),
// so we forward a toggle request rather than mirroring state in main.
label: `Toggle Left Sidebar\t${isMac ? 'Cmd+B' : 'Ctrl+B'}`,
click: () => onToggleLeftSidebar()
},
{
// Why: display-only shortcut hint for the same reason as above.
label: `Toggle Right Sidebar\t${isMac ? 'Alt+Cmd+B' : 'Ctrl+Alt+B'}`,
click: () => onToggleRightSidebar()
},
{
label: 'Show Status Bar',
type: 'checkbox',
checked: appearance.statusBarVisible,
click: () => onToggleAppearance('statusBarVisible')
},
{ type: 'separator' },
{
label: 'Show Tasks Button',
type: 'checkbox',
checked: appearance.showTasksButton,
click: () => onToggleAppearance('showTasksButton')
},
{
label: 'Show Titlebar Agent Activity',
type: 'checkbox',
checked: appearance.showTitlebarAgentActivity,
click: () => onToggleAppearance('showTitlebarAgentActivity')
}
]
}
const viewMenu: Electron.MenuItemConstructorOptions = {
label: 'View',
submenu: [
@@ -242,7 +178,10 @@ function buildAndApplyMenu(options: RegisterAppMenuOptions): void {
{ type: 'separator' },
{ role: 'togglefullscreen' },
{ type: 'separator' },
appearanceSubmenu
{
label: 'Toggle Status Bar',
click: () => onToggleStatusBar()
}
]
}
@@ -271,20 +210,3 @@ function buildAndApplyMenu(options: RegisterAppMenuOptions): void {
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
}
let lastRegisterOptions: RegisterAppMenuOptions | null = null
export function registerAppMenu(options: RegisterAppMenuOptions): void {
lastRegisterOptions = options
buildAndApplyMenu(options)
}
/** Rebuild the application menu using the options from the most recent
* registerAppMenu call. Used to refresh checkbox `checked` state when
* settings that feed the Appearance submenu change, since Electron's
* menu items do not reactively re-render when the backing state updates. */
export function rebuildAppMenu(): void {
if (lastRegisterOptions) {
buildAndApplyMenu(lastRegisterOptions)
}
}
+2 -2
View File
@@ -77,7 +77,7 @@ describe('Store', () => {
expect(settings.terminalFontSize).toBe(14)
expect(settings.terminalFontWeight).toBe(500)
expect(settings.rightSidebarOpenByDefault).toBe(true)
expect(settings.showTasksButton).toBe(true)
expect(settings.showTaskProviderIcons).toBe(true)
})
it('returns default UI state when no data file exists', async () => {
@@ -145,7 +145,7 @@ describe('Store', () => {
expect(store.getSettings().editorAutoSaveDelayMs).toBe(1000)
expect(store.getSettings().refreshLocalBaseRefOnWorktreeCreate).toBe(false)
expect(store.getSettings().rightSidebarOpenByDefault).toBe(true)
expect(store.getSettings().showTasksButton).toBe(true)
expect(store.getSettings().showTaskProviderIcons).toBe(true)
// repos should be loaded
expect(store.getRepos()).toHaveLength(1)
})
-4
View File
@@ -530,10 +530,6 @@ export type PreloadApi = {
set: (args: Partial<GlobalSettings>) => Promise<GlobalSettings>
listFonts: () => Promise<string[]>
previewGhosttyImport: () => Promise<GhosttyImportPreview>
/** Subscribe to out-of-band settings updates (e.g. the View > Appearance
* menu toggles) so the renderer can stay in sync with main's persisted
* state without round-tripping through settings:get. */
onChanged: (callback: (updates: Partial<GlobalSettings>) => void) => () => void
}
codexAccounts: {
list: () => Promise<CodexRateLimitAccountsState>
+1 -10
View File
@@ -608,16 +608,7 @@ const api = {
listFonts: (): Promise<string[]> => ipcRenderer.invoke('settings:listFonts'),
previewGhosttyImport: (): Promise<GhosttyImportPreview> =>
ipcRenderer.invoke('settings:previewGhosttyImport'),
onChanged: (callback: (updates: Record<string, unknown>) => void): (() => void) => {
const listener = (
_event: Electron.IpcRendererEvent,
updates: Record<string, unknown>
): void => callback(updates)
ipcRenderer.on('settings:changed', listener)
return () => ipcRenderer.removeListener('settings:changed', listener)
}
ipcRenderer.invoke('settings:previewGhosttyImport')
},
codexAccounts: {
@@ -34,9 +34,9 @@ export const APPEARANCE_PANE_SEARCH_ENTRIES: SettingsSearchEntry[] = [
keywords: ['titlebar', 'agent', 'badge', 'active', 'count', 'status']
},
{
title: 'Show Tasks Button',
description: 'Show the Tasks button at the top of the left sidebar.',
keywords: ['tasks', 'sidebar', 'button', 'hide', 'show', 'github', 'linear']
title: 'Task Provider Icons',
description: 'Show GitHub and Linear icons in the Tasks sidebar button.',
keywords: ['tasks', 'sidebar', 'github', 'linear', 'icons', 'badges']
}
]
@@ -198,35 +198,36 @@ export function AppearancePane({
<section key="sidebar" className="space-y-4">
<div className="space-y-1">
<h3 className="text-sm font-semibold">Sidebar</h3>
<p className="text-xs text-muted-foreground">Tune the left sidebar chrome.</p>
</div>
<SearchableSetting
title="Show Tasks Button"
description="Show the Tasks button at the top of the left sidebar."
keywords={['tasks', 'sidebar', 'button', 'hide', 'show', 'github', 'linear']}
title="Task Provider Icons"
description="Show GitHub and Linear icons in the Tasks sidebar button."
keywords={['tasks', 'sidebar', 'github', 'linear', 'icons', 'badges']}
className="flex items-center justify-between gap-4 px-1 py-2"
>
<div className="space-y-0.5">
<Label>Show Tasks Button</Label>
<Label>Task Provider Icons</Label>
<p className="text-xs text-muted-foreground">
Show the Tasks button at the top of the left sidebar.
Show GitHub and Linear icons next to the Tasks button.
</p>
</div>
<button
role="switch"
aria-checked={settings.showTasksButton}
aria-checked={settings.showTaskProviderIcons}
onClick={() =>
updateSettings({
showTasksButton: !settings.showTasksButton
showTaskProviderIcons: !settings.showTaskProviderIcons
})
}
className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border border-transparent transition-colors ${
settings.showTasksButton ? 'bg-foreground' : 'bg-muted-foreground/30'
settings.showTaskProviderIcons ? 'bg-foreground' : 'bg-muted-foreground/30'
}`}
>
<span
className={`pointer-events-none block size-3.5 rounded-full bg-background shadow-sm transition-transform ${
settings.showTasksButton ? 'translate-x-4' : 'translate-x-0.5'
settings.showTaskProviderIcons ? 'translate-x-4' : 'translate-x-0.5'
}`}
/>
</button>
@@ -1,20 +1,26 @@
import React from 'react'
import { List } from 'lucide-react'
import { Github, List } from 'lucide-react'
import { useAppStore } from '@/store'
import { useRepoMap } from '@/store/selectors'
import { cn } from '@/lib/utils'
import { isGitRepoKind } from '../../../../shared/repo-kind'
import { getTaskPresetQuery, PER_REPO_FETCH_LIMIT } from '@/lib/new-workspace'
function LinearIcon({ className }: { className?: string }): React.JSX.Element {
return (
<svg viewBox="0 0 24 24" aria-hidden className={className} fill="currentColor">
<path d="M2.886 4.18A11.982 11.982 0 0 1 11.99 0C18.624 0 24 5.376 24 12.009c0 3.64-1.62 6.903-4.18 9.105L2.887 4.18ZM1.817 5.626l16.556 16.556c-.524.33-1.075.62-1.65.866L.951 7.277c.247-.575.537-1.126.866-1.65ZM.322 9.163l14.515 14.515c-.71.172-1.443.282-2.195.322L0 11.358a12 12 0 0 1 .322-2.195Zm-.17 4.862 9.823 9.824a12.02 12.02 0 0 1-9.824-9.824Z" />
</svg>
)
}
const SidebarNav = React.memo(function SidebarNav() {
const openTaskPage = useAppStore((s) => s.openTaskPage)
const activeView = useAppStore((s) => s.activeView)
const repos = useAppStore((s) => s.repos)
const repoMap = useRepoMap()
const canBrowseTasks = repos.some((repo) => isGitRepoKind(repo))
// Why: the setting is opt-out (default true). `!== false` keeps the button
// visible for users whose persisted settings predate this field.
const showTasksButton = useAppStore((s) => s.settings?.showTasksButton !== false)
const showTaskProviderIcons = useAppStore((s) => s.settings?.showTaskProviderIcons !== false)
// Why: warm the GitHub work-item cache on hover/focus so by the time the
// user's click finishes the round-trip has either completed or is already
@@ -45,10 +51,6 @@ const SidebarNav = React.memo(function SidebarNav() {
const tasksActive = activeView === 'tasks'
if (!showTasksButton) {
return null
}
return (
<div className="flex flex-col gap-0.5 px-2 pt-2 pb-1">
<button
@@ -73,6 +75,38 @@ const SidebarNav = React.memo(function SidebarNav() {
>
<List className="size-4 shrink-0" strokeWidth={2.25} />
<span className="flex-1">Tasks</span>
{showTaskProviderIcons ? (
<span className="flex items-center gap-1">
<span
role="button"
tabIndex={-1}
onClick={(e) => {
e.stopPropagation()
if (!canBrowseTasks) {
return
}
openTaskPage({ taskSource: 'github' })
}}
className="rounded p-0.5 text-muted-foreground/70 transition-colors hover:text-foreground"
>
<Github className="size-3.5" aria-hidden />
</span>
<span
role="button"
tabIndex={-1}
onClick={(e) => {
e.stopPropagation()
if (!canBrowseTasks) {
return
}
openTaskPage({ taskSource: 'linear' })
}}
className="rounded p-0.5 text-muted-foreground/70 transition-colors hover:text-foreground"
>
<LinearIcon className="size-3.5" />
</span>
</span>
) : null}
</button>
</div>
)
@@ -178,9 +178,6 @@ describe('useIpcEvents updater integration', () => {
getZoomLevel: () => 0,
set: vi.fn()
},
settings: {
onChanged: () => () => {}
},
updater: {
getStatus: () => Promise.resolve({ state: 'idle' }),
onStatus: (listener: (status: unknown) => void) => {
@@ -368,9 +365,6 @@ describe('useIpcEvents updater integration', () => {
getZoomLevel: () => 0,
set: vi.fn()
},
settings: {
onChanged: () => () => {}
},
updater: {
getStatus: () => Promise.resolve({ state: 'idle' }),
onStatus: () => () => {},
@@ -556,9 +550,6 @@ describe('useIpcEvents updater integration', () => {
getZoomLevel: () => 0,
set: vi.fn()
},
settings: {
onChanged: () => () => {}
},
updater: {
getStatus: () => Promise.resolve({ state: 'idle' }),
onStatus: () => () => {},
@@ -756,9 +747,6 @@ describe('useIpcEvents browser tab close routing', () => {
getZoomLevel: () => 0,
set: vi.fn()
},
settings: {
onChanged: () => () => {}
},
updater: {
getStatus: () => Promise.resolve({ state: 'idle' }),
onStatus: () => () => {},
@@ -941,9 +929,6 @@ describe('useIpcEvents browser tab close routing', () => {
getZoomLevel: () => 0,
set: vi.fn()
},
settings: {
onChanged: () => () => {}
},
updater: {
getStatus: () => Promise.resolve({ state: 'idle' }),
onStatus: () => () => {},
@@ -1121,9 +1106,6 @@ describe('useIpcEvents browser tab close routing', () => {
getZoomLevel: () => 0,
set: vi.fn()
},
settings: {
onChanged: () => () => {}
},
updater: {
getStatus: () => Promise.resolve({ state: 'idle' }),
onStatus: () => () => {},
@@ -1310,9 +1292,6 @@ describe('useIpcEvents shortcut hint clearing', () => {
getZoomLevel: () => 0,
set: vi.fn()
},
settings: {
onChanged: () => () => {}
},
updater: {
getStatus: () => Promise.resolve({ state: 'idle' }),
onStatus: () => () => {},
@@ -1506,9 +1485,6 @@ describe('useIpcEvents CLI-created worktree activation', () => {
getZoomLevel: () => 0,
set: vi.fn()
},
settings: {
onChanged: () => () => {}
},
updater: {
getStatus: () => Promise.resolve({ state: 'idle' }),
onStatus: () => () => {},
-24
View File
@@ -45,30 +45,6 @@ export function useIpcEvents(): void {
})
)
// Why: the View > Appearance menu toggles settings directly in main (so
// checkbox state reflects the persisted value without a round-trip) and
// broadcasts the change. Merge it into the store so the sidebar and
// titlebar re-render immediately instead of waiting for the next
// fetchSettings() call.
unsubs.push(
window.api.settings.onChanged((updates) => {
const store = useAppStore.getState()
if (!store.settings) {
return
}
useAppStore.setState({
settings: {
...store.settings,
...updates,
notifications: {
...store.settings.notifications,
...updates.notifications
}
}
})
})
)
unsubs.push(
window.api.ui.onToggleLeftSidebar(() => {
dispatchClearModifierHints()
+1 -1
View File
@@ -160,7 +160,7 @@ export function getDefaultSettings(homedir: string): GlobalSettings {
openLinksInApp: true,
rightSidebarOpenByDefault: true,
showTitlebarAgentActivity: true,
showTasksButton: true,
showTaskProviderIcons: true,
notifications: getDefaultNotificationSettings(),
diffDefaultView: 'inline',
promptCacheTimerEnabled: false,
+3 -4
View File
@@ -950,10 +950,9 @@ export type GlobalSettings = {
rightSidebarOpenByDefault: boolean
/** Whether to show the live agent activity count badge in the titlebar. */
showTitlebarAgentActivity: boolean
/** Why: some users do not use the Tasks feature and prefer to keep the
* left sidebar free of its button entirely. Hiding the button here also
* removes it from keyboard navigation. */
showTasksButton: boolean
/** Why: the Tasks sidebar label can be kept cleaner for users who do not
* actively use the GitHub/Linear integrations behind it. */
showTaskProviderIcons: boolean
diffDefaultView: 'inline' | 'side-by-side'
notifications: NotificationSettings
/** When true, a countdown timer is shown after a Claude agent becomes idle,