mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 00:03:20 +00:00
Improve right-sidebar edge peek discoverability and add a setting
Teach the edge-hover gesture via the existing toggle tooltip and a one-shot first-close coachmark, and make Arc-style edge peek opt-out in Appearance settings (default on).
This commit is contained in:
@@ -56,6 +56,8 @@ import {
|
||||
} from './components/terminal-pane/use-system-prefers-dark'
|
||||
import RightSidebar from './components/right-sidebar'
|
||||
import { RightSidebarEdgePeekZone } from './components/right-sidebar/right-sidebar-edge-peek'
|
||||
import { RightSidebarEdgePeekTipHost } from './components/right-sidebar/right-sidebar-edge-peek-tip-host'
|
||||
import { isRightSidebarEdgePeekEnabled } from './components/right-sidebar/right-sidebar-edge-peek-preference'
|
||||
import { StarNagCard } from './components/StarNagCard'
|
||||
import { StarNagAgentValueMomentObserver } from './components/star-nag/StarNagAgentValueMomentObserver'
|
||||
import { StarNagToastHost } from './components/star-nag/StarNagToastHost'
|
||||
@@ -2098,6 +2100,7 @@ function App(): React.JSX.Element {
|
||||
</div>
|
||||
)
|
||||
|
||||
const rightSidebarEdgePeekEnabled = isRightSidebarEdgePeekEnabled(settings)
|
||||
const rightSidebarToggle = showRightSidebarControls ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -2109,10 +2112,19 @@ function App(): React.JSX.Element {
|
||||
<PanelRight size={16} />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate('auto.App.c184e056de', 'Toggle right sidebar ({{value0}})', {
|
||||
value0: rightSidebarShortcutLabel
|
||||
})}
|
||||
<TooltipContent side="bottom" sideOffset={6} className="max-w-[220px]">
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span>
|
||||
{translate('auto.App.c184e056de', 'Toggle right sidebar ({{value0}})', {
|
||||
value0: rightSidebarShortcutLabel
|
||||
})}
|
||||
</span>
|
||||
{rightSidebarEdgePeekEnabled ? (
|
||||
<span className="opacity-80">
|
||||
{translate('auto.App.rightSidebarEdgePeekHint', 'Hover the right edge to peek')}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : null
|
||||
@@ -2433,6 +2445,7 @@ function App(): React.JSX.Element {
|
||||
responsive. Unmount on the tasks view since that surface is
|
||||
intentionally distraction-free. */}
|
||||
{showRightSidebarControls ? <RightSidebarEdgePeekZone /> : null}
|
||||
{showRightSidebarControls ? <RightSidebarEdgePeekTipHost /> : null}
|
||||
{showRightSidebarControls ? (
|
||||
<RecoverableRenderErrorBoundary
|
||||
boundaryId="right-sidebar"
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { RightSidebarPanelContent } from './right-sidebar-panel-content'
|
||||
import { useRightSidebarEdgePeekDismiss } from './right-sidebar-edge-peek'
|
||||
import { isRightSidebarEdgePeekEnabled } from './right-sidebar-edge-peek-preference'
|
||||
import { useMeasuredWidth } from './right-sidebar-measured-width'
|
||||
import { normalizeRightSidebarRoute } from '@/store/right-sidebar-route'
|
||||
import { AgentSessionHistoryIcon } from './agent-session-history-icon'
|
||||
@@ -65,6 +66,7 @@ function RightSidebarInner(): React.JSX.Element {
|
||||
const rightSidebarOpen = useAppStore((s) => s.rightSidebarOpen)
|
||||
const rightSidebarPeek = useAppStore((s) => s.rightSidebarPeek)
|
||||
const setRightSidebarPeek = useAppStore((s) => s.setRightSidebarPeek)
|
||||
const rightSidebarEdgePeekEnabled = useAppStore((s) => isRightSidebarEdgePeekEnabled(s.settings))
|
||||
const rightSidebarWidth = useAppStore((s) => s.rightSidebarWidth)
|
||||
const setRightSidebarWidth = useAppStore((s) => s.setRightSidebarWidth)
|
||||
const rightSidebarTab = useAppStore((s) => s.rightSidebarTab)
|
||||
@@ -280,12 +282,24 @@ function RightSidebarInner(): React.JSX.Element {
|
||||
<PanelRight size={16} />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate(
|
||||
'auto.components.right.sidebar.index.9fffaf17c1',
|
||||
'Toggle right sidebar ({{value0}})',
|
||||
{ value0: rightSidebarShortcut }
|
||||
)}
|
||||
<TooltipContent side="bottom" sideOffset={6} className="max-w-[220px]">
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span>
|
||||
{translate(
|
||||
'auto.components.right.sidebar.index.9fffaf17c1',
|
||||
'Toggle right sidebar ({{value0}})',
|
||||
{ value0: rightSidebarShortcut }
|
||||
)}
|
||||
</span>
|
||||
{rightSidebarEdgePeekEnabled ? (
|
||||
<span className="opacity-80">
|
||||
{translate(
|
||||
'auto.components.right.sidebar.index.edgePeekHint',
|
||||
'Hover the right edge to peek'
|
||||
)}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : null
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
isRightSidebarEdgePeekEnabled,
|
||||
isRightSidebarEdgePeekTipDismissed
|
||||
} from './right-sidebar-edge-peek-preference'
|
||||
|
||||
describe('isRightSidebarEdgePeekEnabled', () => {
|
||||
it('defaults on when settings are missing or the flag is absent', () => {
|
||||
expect(isRightSidebarEdgePeekEnabled(null)).toBe(true)
|
||||
expect(isRightSidebarEdgePeekEnabled(undefined)).toBe(true)
|
||||
expect(isRightSidebarEdgePeekEnabled({})).toBe(true)
|
||||
})
|
||||
|
||||
it('respects an explicit opt-out', () => {
|
||||
expect(isRightSidebarEdgePeekEnabled({ rightSidebarEdgePeekEnabled: false })).toBe(false)
|
||||
expect(isRightSidebarEdgePeekEnabled({ rightSidebarEdgePeekEnabled: true })).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isRightSidebarEdgePeekTipDismissed', () => {
|
||||
it('defaults to not dismissed', () => {
|
||||
expect(isRightSidebarEdgePeekTipDismissed(null)).toBe(false)
|
||||
expect(isRightSidebarEdgePeekTipDismissed({})).toBe(false)
|
||||
})
|
||||
|
||||
it('returns true only for an explicit dismiss', () => {
|
||||
expect(isRightSidebarEdgePeekTipDismissed({ rightSidebarEdgePeekTipDismissed: true })).toBe(
|
||||
true
|
||||
)
|
||||
expect(isRightSidebarEdgePeekTipDismissed({ rightSidebarEdgePeekTipDismissed: false })).toBe(
|
||||
false
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { GlobalSettings } from '../../../../shared/types'
|
||||
|
||||
/** Absent means on: edge peek is default-enabled for legacy profiles. */
|
||||
export function isRightSidebarEdgePeekEnabled(
|
||||
settings: Pick<GlobalSettings, 'rightSidebarEdgePeekEnabled'> | null | undefined
|
||||
): boolean {
|
||||
return settings?.rightSidebarEdgePeekEnabled !== false
|
||||
}
|
||||
|
||||
export function isRightSidebarEdgePeekTipDismissed(
|
||||
settings: Pick<GlobalSettings, 'rightSidebarEdgePeekTipDismissed'> | null | undefined
|
||||
): boolean {
|
||||
return settings?.rightSidebarEdgePeekTipDismissed === true
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useAppStore } from '@/store'
|
||||
import {
|
||||
dismissRightSidebarEdgePeekTipToast,
|
||||
shouldShowRightSidebarEdgePeekTip,
|
||||
showRightSidebarEdgePeekTip
|
||||
} from './right-sidebar-edge-peek-tip'
|
||||
import { isRightSidebarEdgePeekEnabled } from './right-sidebar-edge-peek-preference'
|
||||
|
||||
/**
|
||||
* One-shot coachmark when the user first closes the right sidebar while edge
|
||||
* peek is enabled. Successful peeks also dismiss the tip (they already found
|
||||
* the gesture). Renders nothing.
|
||||
*/
|
||||
export function RightSidebarEdgePeekTipHost(): null {
|
||||
const rightSidebarOpen = useAppStore((s) => s.rightSidebarOpen)
|
||||
const rightSidebarPeek = useAppStore((s) => s.rightSidebarPeek)
|
||||
const settings = useAppStore((s) => s.settings)
|
||||
const updateSettings = useAppStore((s) => s.updateSettings)
|
||||
// Why: only fire on a true open→closed transition, not on first mount when
|
||||
// the sidebar happens to start closed (no prior close gesture to teach from).
|
||||
const prevOpenRef = useRef(rightSidebarOpen)
|
||||
|
||||
useEffect(() => {
|
||||
const wasOpen = prevOpenRef.current
|
||||
prevOpenRef.current = rightSidebarOpen
|
||||
if (!wasOpen || rightSidebarOpen) {
|
||||
return
|
||||
}
|
||||
if (!shouldShowRightSidebarEdgePeekTip(settings)) {
|
||||
return
|
||||
}
|
||||
showRightSidebarEdgePeekTip({ updateSettings })
|
||||
}, [rightSidebarOpen, settings, updateSettings])
|
||||
|
||||
useEffect(() => {
|
||||
// Why: if they discover the edge themselves, mark the tip seen so we never
|
||||
// nag about a gesture they already use.
|
||||
if (!rightSidebarPeek || !isRightSidebarEdgePeekEnabled(settings)) {
|
||||
return
|
||||
}
|
||||
if (settings?.rightSidebarEdgePeekTipDismissed === true) {
|
||||
return
|
||||
}
|
||||
dismissRightSidebarEdgePeekTipToast()
|
||||
void Promise.resolve(updateSettings({ rightSidebarEdgePeekTipDismissed: true })).catch(() => {})
|
||||
}, [rightSidebarPeek, settings, updateSettings])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isRightSidebarEdgePeekEnabled(settings)) {
|
||||
dismissRightSidebarEdgePeekTipToast()
|
||||
}
|
||||
}, [settings])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { toast } from 'sonner'
|
||||
import {
|
||||
RIGHT_SIDEBAR_EDGE_PEEK_TIP_TOAST_ID,
|
||||
shouldShowRightSidebarEdgePeekTip,
|
||||
showRightSidebarEdgePeekTip
|
||||
} from './right-sidebar-edge-peek-tip'
|
||||
|
||||
vi.mock('sonner', () => ({
|
||||
toast: {
|
||||
info: vi.fn(),
|
||||
dismiss: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
describe('shouldShowRightSidebarEdgePeekTip', () => {
|
||||
it('shows only when edge peek is enabled and the tip is not dismissed', () => {
|
||||
expect(shouldShowRightSidebarEdgePeekTip(null)).toBe(true)
|
||||
expect(
|
||||
shouldShowRightSidebarEdgePeekTip({
|
||||
rightSidebarEdgePeekEnabled: true,
|
||||
rightSidebarEdgePeekTipDismissed: false
|
||||
})
|
||||
).toBe(true)
|
||||
expect(
|
||||
shouldShowRightSidebarEdgePeekTip({
|
||||
rightSidebarEdgePeekEnabled: false
|
||||
})
|
||||
).toBe(false)
|
||||
expect(
|
||||
shouldShowRightSidebarEdgePeekTip({
|
||||
rightSidebarEdgePeekTipDismissed: true
|
||||
})
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('showRightSidebarEdgePeekTip', () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(toast.info).mockReset()
|
||||
})
|
||||
|
||||
it('shows a dismissible info toast and persists dismiss on close', () => {
|
||||
const updateSettings = vi.fn().mockResolvedValue(undefined)
|
||||
showRightSidebarEdgePeekTip({ updateSettings })
|
||||
|
||||
expect(toast.info).toHaveBeenCalledTimes(1)
|
||||
const [, options] = vi.mocked(toast.info).mock.calls[0] as [
|
||||
string,
|
||||
{
|
||||
id: string
|
||||
onDismiss?: () => void
|
||||
onAutoClose?: () => void
|
||||
}
|
||||
]
|
||||
expect(options.id).toBe(RIGHT_SIDEBAR_EDGE_PEEK_TIP_TOAST_ID)
|
||||
|
||||
options.onDismiss?.()
|
||||
expect(updateSettings).toHaveBeenCalledWith({ rightSidebarEdgePeekTipDismissed: true })
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,49 @@
|
||||
import { toast } from 'sonner'
|
||||
import type { GlobalSettings } from '../../../../shared/types'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import {
|
||||
isRightSidebarEdgePeekEnabled,
|
||||
isRightSidebarEdgePeekTipDismissed
|
||||
} from './right-sidebar-edge-peek-preference'
|
||||
|
||||
export const RIGHT_SIDEBAR_EDGE_PEEK_TIP_TOAST_ID = 'right-sidebar-edge-peek-tip'
|
||||
|
||||
type TipSettings = Pick<
|
||||
GlobalSettings,
|
||||
'rightSidebarEdgePeekEnabled' | 'rightSidebarEdgePeekTipDismissed'
|
||||
> | null
|
||||
|
||||
export function shouldShowRightSidebarEdgePeekTip(settings: TipSettings): boolean {
|
||||
return isRightSidebarEdgePeekEnabled(settings) && !isRightSidebarEdgePeekTipDismissed(settings)
|
||||
}
|
||||
|
||||
export function showRightSidebarEdgePeekTip(args: {
|
||||
updateSettings: (updates: Partial<GlobalSettings>) => Promise<void> | void
|
||||
}): void {
|
||||
const dismissTip = (): void => {
|
||||
void Promise.resolve(args.updateSettings({ rightSidebarEdgePeekTipDismissed: true })).catch(
|
||||
() => {}
|
||||
)
|
||||
}
|
||||
|
||||
// Why: one-shot discoverability for an invisible edge gesture; long enough
|
||||
// to read but not sticky like opt-in setting suggestions.
|
||||
toast.info(
|
||||
translate('auto.components.right.sidebar.edge.peek.tip.title', 'Hover the right edge to peek'),
|
||||
{
|
||||
id: RIGHT_SIDEBAR_EDGE_PEEK_TIP_TOAST_ID,
|
||||
description: translate(
|
||||
'auto.components.right.sidebar.edge.peek.tip.description',
|
||||
'The right sidebar slides over the editor without reflowing it. Click the toggle or use the shortcut to pin it open. Change this anytime in Settings › Appearance.'
|
||||
),
|
||||
duration: 10_000,
|
||||
dismissible: true,
|
||||
onDismiss: dismissTip,
|
||||
onAutoClose: dismissTip
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export function dismissRightSidebarEdgePeekTipToast(): void {
|
||||
toast.dismiss(RIGHT_SIDEBAR_EDGE_PEEK_TIP_TOAST_ID)
|
||||
}
|
||||
@@ -88,6 +88,33 @@ describe('RightSidebarEdgePeekZone', () => {
|
||||
expect(useAppStore.getState().rightSidebarPeek).toBe(false)
|
||||
})
|
||||
|
||||
it('does not arm when edge peek is disabled in settings', () => {
|
||||
useAppStore.setState({
|
||||
settings: { rightSidebarEdgePeekEnabled: false } as never
|
||||
})
|
||||
render(<RightSidebarEdgePeekZone />)
|
||||
|
||||
moveMouse(RIGHT_EDGE_X, BELOW_TITLEBAR_Y)
|
||||
vi.advanceTimersByTime(PEEK_OPEN_DELAY_MS)
|
||||
|
||||
expect(useAppStore.getState().rightSidebarPeek).toBe(false)
|
||||
})
|
||||
|
||||
it('clears an active peek when edge peek is turned off', () => {
|
||||
useAppStore.setState({ rightSidebarPeek: true })
|
||||
const { rerender } = render(<RightSidebarEdgePeekZone />)
|
||||
expect(useAppStore.getState().rightSidebarPeek).toBe(true)
|
||||
|
||||
act(() => {
|
||||
useAppStore.setState({
|
||||
settings: { rightSidebarEdgePeekEnabled: false } as never
|
||||
})
|
||||
})
|
||||
rerender(<RightSidebarEdgePeekZone />)
|
||||
|
||||
expect(useAppStore.getState().rightSidebarPeek).toBe(false)
|
||||
})
|
||||
|
||||
it('clears an active peek when the zone unmounts on a view change', () => {
|
||||
const { unmount } = render(<RightSidebarEdgePeekZone />)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { useAppStore } from '@/store'
|
||||
import { isRightSidebarEdgePeekEnabled } from './right-sidebar-edge-peek-preference'
|
||||
|
||||
// Arc-style edge peek: hovering the window's right edge while the sidebar is
|
||||
// closed reveals it as a floating overlay instead of reflowing the editor.
|
||||
@@ -21,6 +22,7 @@ export function RightSidebarEdgePeekZone(): React.JSX.Element | null {
|
||||
const rightSidebarOpen = useAppStore((s) => s.rightSidebarOpen)
|
||||
const rightSidebarPeek = useAppStore((s) => s.rightSidebarPeek)
|
||||
const setRightSidebarPeek = useAppStore((s) => s.setRightSidebarPeek)
|
||||
const edgePeekEnabled = useAppStore((s) => isRightSidebarEdgePeekEnabled(s.settings))
|
||||
|
||||
useEffect(() => {
|
||||
// Why: the zone unmounts when the active view loses sidebar controls; a
|
||||
@@ -29,11 +31,20 @@ export function RightSidebarEdgePeekZone(): React.JSX.Element | null {
|
||||
return () => setRightSidebarPeek(false)
|
||||
}, [setRightSidebarPeek])
|
||||
|
||||
useEffect(() => {
|
||||
// Why: an active peek must collapse immediately when the user turns the
|
||||
// setting off mid-gesture so the overlay doesn't stick without a dismiss
|
||||
// path that matches their preference.
|
||||
if (!edgePeekEnabled && rightSidebarPeek) {
|
||||
setRightSidebarPeek(false)
|
||||
}
|
||||
}, [edgePeekEnabled, rightSidebarPeek, setRightSidebarPeek])
|
||||
|
||||
useEffect(() => {
|
||||
// The revealed overlay covers the edge, so arming is only needed while the
|
||||
// sidebar is fully hidden. Skipping the listener here also guarantees an
|
||||
// armed timer can't fire after the sidebar opens (the cleanup clears it).
|
||||
if (rightSidebarOpen || rightSidebarPeek) {
|
||||
if (!edgePeekEnabled || rightSidebarOpen || rightSidebarPeek) {
|
||||
return
|
||||
}
|
||||
let openTimer: number | null = null
|
||||
@@ -65,7 +76,7 @@ export function RightSidebarEdgePeekZone(): React.JSX.Element | null {
|
||||
clearOpenTimer()
|
||||
window.removeEventListener('mousemove', onMouseMove)
|
||||
}
|
||||
}, [rightSidebarOpen, rightSidebarPeek, setRightSidebarPeek])
|
||||
}, [edgePeekEnabled, rightSidebarOpen, rightSidebarPeek, setRightSidebarPeek])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@ import { getLayoutEntries, getSidebarEntries, getStatusBarToggles } from './appe
|
||||
import { LeftSidebarAppearanceSetting } from './LeftSidebarAppearanceSetting'
|
||||
import {
|
||||
getLeftSidebarAppearanceEntry,
|
||||
getRightSidebarEdgePeekEntry,
|
||||
getWorkspaceCardLayoutEntry
|
||||
} from './appearance-sidebar-search'
|
||||
import { isRightSidebarEdgePeekEnabled } from '../right-sidebar/right-sidebar-edge-peek-preference'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { matchesSettingsSearch, normalizeSettingsSearchQuery } from './settings-search'
|
||||
|
||||
@@ -62,9 +64,16 @@ export function AppearanceWindowSidebarSection({
|
||||
const setWorktreeCardMode = useAppStore((state) => state.setWorktreeCardMode)
|
||||
const visibleStatusBarToggles = useAvailableStatusBarToggles(getStatusBarToggles())
|
||||
const leftSidebarAppearanceEntry = getLeftSidebarAppearanceEntry()
|
||||
const rightSidebarEdgePeekEntry = getRightSidebarEdgePeekEntry()
|
||||
const sidebarEntries = getSidebarEntries()
|
||||
const workspaceCardLayoutEntry = getWorkspaceCardLayoutEntry()
|
||||
const layoutEntries = getLayoutEntries()
|
||||
// Why: sidebarEntries[0] is the edge-peek entry; later indices keep the
|
||||
// existing left-sidebar button toggles that this section already renders.
|
||||
const showTasksEntry = sidebarEntries[1]
|
||||
const showAutomationsEntry = sidebarEntries[2]
|
||||
const showMobileEntry = sidebarEntries[3]
|
||||
const edgePeekEnabled = isRightSidebarEdgePeekEnabled(settings)
|
||||
const statusBarTitle = translate(
|
||||
'auto.components.settings.AppearancePane.3e4175e5c6',
|
||||
'Status Bar'
|
||||
@@ -191,13 +200,29 @@ export function AppearanceWindowSidebarSection({
|
||||
/>
|
||||
</SearchableSetting>
|
||||
|
||||
<SearchableSetting
|
||||
title={rightSidebarEdgePeekEntry.title}
|
||||
description={rightSidebarEdgePeekEntry.description}
|
||||
keywords={rightSidebarEdgePeekEntry.keywords}
|
||||
>
|
||||
<SettingsSwitchRow
|
||||
label={rightSidebarEdgePeekEntry.title}
|
||||
description={rightSidebarEdgePeekEntry.description}
|
||||
checked={edgePeekEnabled}
|
||||
onChange={() =>
|
||||
updateSettings({ rightSidebarEdgePeekEnabled: !edgePeekEnabled })
|
||||
}
|
||||
ariaLabel={rightSidebarEdgePeekEntry.title}
|
||||
/>
|
||||
</SearchableSetting>
|
||||
|
||||
<SearchableSetting
|
||||
title={translate(
|
||||
'auto.components.settings.AppearancePane.cf81907069',
|
||||
'Show Tasks Button'
|
||||
)}
|
||||
description={sidebarEntries[0]?.description}
|
||||
keywords={sidebarEntries[0]?.keywords ?? ['tasks', 'sidebar', 'button']}
|
||||
description={showTasksEntry?.description}
|
||||
keywords={showTasksEntry?.keywords ?? ['tasks', 'sidebar', 'button']}
|
||||
>
|
||||
<SettingsSwitchRow
|
||||
label={translate(
|
||||
@@ -216,9 +241,9 @@ export function AppearanceWindowSidebarSection({
|
||||
'auto.components.settings.AppearancePane.511f270ebb',
|
||||
'Show Automations Button'
|
||||
)}
|
||||
description={sidebarEntries[1]?.description}
|
||||
description={showAutomationsEntry?.description}
|
||||
keywords={
|
||||
sidebarEntries[1]?.keywords ?? ['automations', 'automation', 'schedule']
|
||||
showAutomationsEntry?.keywords ?? ['automations', 'automation', 'schedule']
|
||||
}
|
||||
>
|
||||
<SettingsSwitchRow
|
||||
@@ -240,8 +265,8 @@ export function AppearanceWindowSidebarSection({
|
||||
'auto.components.settings.AppearancePane.9da1020447',
|
||||
'Show Orca Mobile Button'
|
||||
)}
|
||||
description={sidebarEntries[2]?.description}
|
||||
keywords={sidebarEntries[2]?.keywords ?? ['mobile', 'phone', 'sidebar']}
|
||||
description={showMobileEntry?.description}
|
||||
keywords={showMobileEntry?.keywords ?? ['mobile', 'phone', 'sidebar']}
|
||||
>
|
||||
<SettingsSwitchRow
|
||||
label={translate(
|
||||
|
||||
@@ -79,7 +79,48 @@ export const getWorkspaceCardLayoutEntry = createLocalizedCatalog(
|
||||
})
|
||||
)
|
||||
|
||||
export const getRightSidebarEdgePeekEntry = createLocalizedCatalog(
|
||||
(): SettingsSearchEntry => ({
|
||||
title: translate(
|
||||
'auto.components.settings.appearance.search.rightSidebarEdgePeek.title',
|
||||
'Right Sidebar Edge Peek'
|
||||
),
|
||||
description: translate(
|
||||
'auto.components.settings.appearance.search.rightSidebarEdgePeek.description',
|
||||
'Hover the window edge to temporarily reveal a closed right sidebar.'
|
||||
),
|
||||
keywords: [
|
||||
...translateSearchKeyword('auto.components.settings.appearance.search.5bff6a2ef0', 'sidebar'),
|
||||
...translateSearchKeyword(
|
||||
'auto.components.settings.appearance.search.rightSidebarEdgePeek.right',
|
||||
'right sidebar'
|
||||
),
|
||||
...translateSearchKeyword(
|
||||
'auto.components.settings.appearance.search.rightSidebarEdgePeek.peek',
|
||||
'peek'
|
||||
),
|
||||
...translateSearchKeyword(
|
||||
'auto.components.settings.appearance.search.rightSidebarEdgePeek.hover',
|
||||
'hover'
|
||||
),
|
||||
...translateSearchKeyword(
|
||||
'auto.components.settings.appearance.search.rightSidebarEdgePeek.edge',
|
||||
'edge'
|
||||
),
|
||||
...translateSearchKeyword(
|
||||
'auto.components.settings.appearance.search.rightSidebarEdgePeek.overlay',
|
||||
'overlay'
|
||||
),
|
||||
...translateSearchKeyword(
|
||||
'auto.components.settings.appearance.search.rightSidebarEdgePeek.arc',
|
||||
'arc'
|
||||
)
|
||||
]
|
||||
})
|
||||
)
|
||||
|
||||
export const getSidebarEntries = createLocalizedCatalog((): SettingsSearchEntry[] => [
|
||||
getRightSidebarEdgePeekEntry(),
|
||||
{
|
||||
title: translate('auto.components.settings.appearance.search.155a1e7438', 'Show Tasks Button'),
|
||||
description: translate(
|
||||
|
||||
@@ -275,6 +275,8 @@ export function getDefaultSettings(homedir: string): GlobalSettings {
|
||||
experimentalNativeChat: false,
|
||||
openInApplications: [...DEFAULT_OPEN_IN_APPLICATIONS],
|
||||
rightSidebarOpenByDefault: true,
|
||||
rightSidebarEdgePeekEnabled: true,
|
||||
rightSidebarEdgePeekTipDismissed: false,
|
||||
showGitIgnoredFiles: true,
|
||||
sourceControlViewMode: 'list',
|
||||
sourceControlGroupOrder: DEFAULT_SOURCE_CONTROL_GROUP_ORDER,
|
||||
|
||||
@@ -2642,6 +2642,14 @@ export type GlobalSettings = {
|
||||
openInApplications?: OpenInApplication[]
|
||||
/** Deprecated: migration/backward-compat only. Use PersistedUIState.rightSidebarOpen. */
|
||||
rightSidebarOpenByDefault: boolean
|
||||
/** When enabled (default), hovering near the window's right edge while the
|
||||
* right sidebar is closed peeks it as a floating overlay. Opt-out for users
|
||||
* who only want the explicit toggle/shortcut. Optional for legacy profiles;
|
||||
* readers treat absent as true. */
|
||||
rightSidebarEdgePeekEnabled?: boolean
|
||||
/** One-shot coachmark after first closing the right sidebar while edge peek
|
||||
* is enabled. Absent/false means the tip can still show. */
|
||||
rightSidebarEdgePeekTipDismissed?: boolean
|
||||
showGitIgnoredFiles?: boolean
|
||||
/** Preferred Source Control changes layout. Per-user, not per-workspace. */
|
||||
sourceControlViewMode: SourceControlViewMode
|
||||
|
||||
Reference in New Issue
Block a user