mirror of
https://github.com/stablyai/orca.git
synced 2026-10-13 00:02:54 +00:00
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:
@@ -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,
|
||||
|
||||
@@ -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
@@ -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({
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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
@@ -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: () => () => {},
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user