feat(desktop): add reset-to-defaults to the statusbar context menu

Once you have toggled a few items on and off there is no way to get
back to the shipped layout short of remembering which ids are in
STATUSBAR_HIDDEN_BY_DEFAULT. Add a row to the bar's right-click menu
that restores that set.

The row is disabled rather than hidden when nothing is customized, so
it also advertises that a shipped layout exists. Reset touches item
layout only — whole-bar visibility is a separate preference, and
resetting from the bar's own menu should not make the bar you are
right-clicking disappear.
This commit is contained in:
Brooklyn Nicholson
2026-08-13 13:54:15 -05:00
committed by brooklyn!
parent c692312704
commit e505ff9777
6 changed files with 101 additions and 1 deletions

View File

@@ -17,7 +17,13 @@ import { ContribRender } from '@/contrib/react/boundary'
import { useI18n } from '@/i18n'
import { useKeybindHint } from '@/lib/keybinds/use-keybind-hint'
import { cn } from '@/lib/utils'
import { $statusbarHiddenIds, setStatusbarItemVisible, toggleStatusbarVisible } from '@/store/statusbar-prefs'
import {
$statusbarHiddenIds,
isStatusbarLayoutDefault,
resetStatusbarLayout,
setStatusbarItemVisible,
toggleStatusbarVisible
} from '@/store/statusbar-prefs'
// Shared chrome styling for interactive statusbar items (button / link / menu
// trigger). The 'text' variant intentionally omits hover/transition/disabled.
@@ -176,6 +182,18 @@ function StatusbarVisibilityMenu({
</ContextMenuCheckboxItem>
))}
<ContextMenuSeparator />
{/* Disabled rather than hidden when nothing is customized: the row is
also how you find out there IS a shipped layout to get back to.
Groups with the hide row below — both act on the bar, not an item. */}
<ContextMenuItem
disabled={isStatusbarLayoutDefault(hiddenIds)}
onSelect={event => {
event.preventDefault()
resetStatusbarLayout()
}}
>
<span className="truncate">{copy.resetStatusbar}</span>
</ContextMenuItem>
</>
)}
<ContextMenuItem onSelect={toggleStatusbarVisible}>

View File

@@ -125,6 +125,68 @@ describe('statusbar item visibility', () => {
})
})
describe('reset to defaults', () => {
it('puts a customized bar back to the shipped show/hide set', async () => {
$statusbarHiddenIds.set(['gateway-health'])
const statusbar = bar([item('cron', 'Cron'), item('gateway-health', 'Gateway')])
expect(screen.queryByText('Gateway')).toBeNull()
expect(within(statusbar).getByText('Cron')).toBeTruthy()
openContextMenu(statusbar)
fireEvent.click(await screen.findByRole('menuitem', { name: /reset to defaults/i }))
expect($statusbarHiddenIds.get()).toEqual([...STATUSBAR_HIDDEN_BY_DEFAULT])
expect(within(statusbar).getByText('Gateway')).toBeTruthy()
// Scoped to the bar: the menu stays open after a reset, so an unscoped query
// matches its still-listed 'Cron' checkbox row rather than a bar item.
expect(within(statusbar).queryByText('Cron')).toBeNull()
})
it('disables the row when the layout is already default', async () => {
const statusbar = bar([item('cron', 'Cron'), item('gateway-health', 'Gateway')])
openContextMenu(statusbar)
const row = await screen.findByRole('menuitem', { name: /reset to defaults/i })
expect(row.getAttribute('data-disabled')).not.toBeNull()
})
it('enables the row as soon as one item differs, in either direction', async () => {
const statusbar = bar([item('cron', 'Cron'), item('gateway-health', 'Gateway')])
// Showing a default-hidden item counts…
$statusbarHiddenIds.set(STATUSBAR_HIDDEN_BY_DEFAULT.filter(id => id !== 'cron'))
openContextMenu(statusbar)
expect(
(await screen.findByRole('menuitem', { name: /reset to defaults/i })).getAttribute('data-disabled')
).toBeNull()
// …and so does hiding a default-shown one.
$statusbarHiddenIds.set([...STATUSBAR_HIDDEN_BY_DEFAULT, 'gateway-health'])
expect(
(await screen.findByRole('menuitem', { name: /reset to defaults/i })).getAttribute('data-disabled')
).toBeNull()
})
it('leaves whole-bar visibility alone — reset is about items, not the bar', async () => {
// Set to the NON-default so a reset that wrongly restored bar visibility too
// would flip this back to true and fail. StatusbarControls doesn't read the
// atom (the controller gates the mount), so the menu is still reachable here.
$statusbarVisible.set(false)
$statusbarHiddenIds.set([])
const statusbar = bar([item('gateway-health', 'Gateway')])
openContextMenu(statusbar)
fireEvent.click(await screen.findByRole('menuitem', { name: /reset to defaults/i }))
expect($statusbarHiddenIds.get()).toEqual([...STATUSBAR_HIDDEN_BY_DEFAULT])
expect($statusbarVisible.get()).toBe(false)
})
})
describe('whole-bar visibility', () => {
it('hides the bar from the context menu, leaving the keybind as the way back', async () => {
const statusbar = bar([item('gateway-health', 'Gateway')])

View File

@@ -2561,6 +2561,7 @@ export const en: Translations = {
gatewayTitle: 'Gateway',
customizeTitle: 'Show in status bar',
hideStatusbar: 'Hide status bar',
resetStatusbar: 'Reset to defaults',
toggleApprovalMode: 'Approvals',
toggleBackendVersion: 'Backend version',
toggleCommandCenter: 'Command Center',

View File

@@ -2153,6 +2153,7 @@ export interface Translations {
gatewayTitle: string
customizeTitle: string
hideStatusbar: string
resetStatusbar: string
toggleApprovalMode: string
toggleBackendVersion: string
toggleCommandCenter: string

View File

@@ -2739,6 +2739,7 @@ export const zh: Translations = {
gatewayTitle: '网关',
customizeTitle: '在状态栏中显示',
hideStatusbar: '隐藏状态栏',
resetStatusbar: '恢复默认设置',
toggleApprovalMode: '审批',
toggleBackendVersion: '后端版本',
toggleCommandCenter: '命令中心',

View File

@@ -51,3 +51,20 @@ export function setStatusbarItemVisible(id: string, visible: boolean) {
$statusbarHiddenIds.set(visible ? hidden.filter(entry => entry !== id) : [...hidden, id])
}
/** Pure so the menu can derive its reset row's disabled state from the hidden
* list it already subscribes to, rather than reading the atom out of band.
* Set-compared: order is incidental (items are appended as they're hidden) and
* a duplicated id shouldn't read as a customization. */
export function isStatusbarLayoutDefault(hidden: readonly string[]) {
const ids = new Set(hidden)
return ids.size === STATUSBAR_HIDDEN_BY_DEFAULT.length && STATUSBAR_HIDDEN_BY_DEFAULT.every(id => ids.has(id))
}
/** Put the show/hide set back to what ships. Only touches item layout — whole-bar
* visibility is a separate preference, and resetting from the bar's own menu
* shouldn't make the bar the user is right-clicking disappear. */
export function resetStatusbarLayout() {
$statusbarHiddenIds.set([...STATUSBAR_HIDDEN_BY_DEFAULT])
}