mirror of
https://github.com/stablyai/orca.git
synced 2026-10-19 00:03:20 +00:00
Fix right sidebar peek visibility and drag handling
This commit is contained in:
@@ -25,6 +25,7 @@ import { useActiveWorktree, useRepoById } from '@/store/selectors'
|
||||
import { useChecksPanelTerminalWorktree } from './use-checks-panel-terminal-worktree'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { openHttpLink } from '@/lib/http-link-routing'
|
||||
import { isRightSidebarRevealed } from '@/lib/right-sidebar-visibility'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { DetachedHeadBadge } from '@/components/DetachedHeadBadge'
|
||||
import {
|
||||
@@ -374,9 +375,9 @@ export default function ChecksPanel(): React.JSX.Element {
|
||||
// Why: the sidebar stays mounted when closed (for performance). Gate
|
||||
// polling on visibility so we don't fetch checks/comments — or poll the
|
||||
// terminal cwd — in the background when the panel isn't visible.
|
||||
const rightSidebarOpen = useAppStore((s) => s.rightSidebarOpen)
|
||||
const rightSidebarRevealed = useAppStore(isRightSidebarRevealed)
|
||||
const rightSidebarTab = useAppStore((s) => s.rightSidebarTab)
|
||||
const isPanelVisible = rightSidebarOpen && rightSidebarTab === 'checks'
|
||||
const isPanelVisible = rightSidebarRevealed && rightSidebarTab === 'checks'
|
||||
|
||||
// Follow the active terminal's cwd so linked-PR/checks state tracks the
|
||||
// worktree the terminal is actually operating in (e.g. across a stack),
|
||||
|
||||
@@ -55,6 +55,7 @@ import { CLOSE_ALL_CONTEXT_MENUS_EVENT } from '@/components/tab-bar/SortableTab'
|
||||
import type { RightSidebarExplorerView } from '../../../../shared/types'
|
||||
import { getRuntimeEnvironmentIdForWorktree } from '@/lib/worktree-runtime-owner'
|
||||
import { createNewTerminalTab } from '@/components/terminal/terminal-tab-actions'
|
||||
import { isRightSidebarRevealed } from '@/lib/right-sidebar-visibility'
|
||||
|
||||
function FileExplorerFiles(): React.JSX.Element {
|
||||
const explorerView = useAppStore((s) => s.rightSidebarExplorerView)
|
||||
@@ -97,7 +98,7 @@ function FileExplorerFiles(): React.JSX.Element {
|
||||
const openFiles = useAppStore((s) => s.openFiles)
|
||||
const closeFile = useAppStore((s) => s.closeFile)
|
||||
const openModal = useAppStore((s) => s.openModal)
|
||||
const rightSidebarOpen = useAppStore((s) => s.rightSidebarOpen)
|
||||
const rightSidebarRevealed = useAppStore(isRightSidebarRevealed)
|
||||
const showDotfiles = useAppStore((s) =>
|
||||
activeWorktreeId ? (s.showDotfilesByWorktree[activeWorktreeId] ?? true) : true
|
||||
)
|
||||
@@ -119,7 +120,7 @@ function FileExplorerFiles(): React.JSX.Element {
|
||||
const isFilesViewActive = explorerView === 'files'
|
||||
const visibleFilesWorktreePath = getVisibleFileExplorerWorktreePath({
|
||||
explorerView,
|
||||
rightSidebarOpen,
|
||||
rightSidebarOpen: rightSidebarRevealed,
|
||||
worktreePath
|
||||
})
|
||||
const repoName = activeRepo?.displayName ?? (worktreePath ? basename(worktreePath) : '')
|
||||
|
||||
@@ -41,6 +41,7 @@ import { detectLanguage } from '@/lib/language-detect'
|
||||
import { basename, dirname, joinPath } from '@/lib/path'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { WORKSPACE_FILE_PATH_MIME } from '@/lib/workspace-file-drag'
|
||||
import { isRightSidebarRevealed } from '@/lib/right-sidebar-visibility'
|
||||
import { isFolderRepo } from '../../../../shared/repo-kind'
|
||||
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '@/components/ui/tooltip'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -1228,12 +1229,12 @@ function SourceControlInner(): React.JSX.Element {
|
||||
recordKey: activePullRequestGenerationKey,
|
||||
record: activePullRequestGenerationRecord
|
||||
})
|
||||
const rightSidebarOpen = useAppStore((s) => s.rightSidebarOpen)
|
||||
// Why: gate polling on both the active tab AND the sidebar being open.
|
||||
const rightSidebarRevealed = useAppStore(isRightSidebarRevealed)
|
||||
// Why: gate polling on both the active tab AND the sidebar being revealed.
|
||||
// The sidebar now stays mounted when closed (for performance), so without
|
||||
// this guard the branchCompare interval and PR fetch would keep running
|
||||
// with no visible consumer, wasting git process spawns and API calls.
|
||||
const isBranchVisible = rightSidebarTab === 'source-control' && rightSidebarOpen
|
||||
const isBranchVisible = rightSidebarTab === 'source-control' && rightSidebarRevealed
|
||||
|
||||
const refreshActiveGitStatus = useCallback(async (): Promise<void> => {
|
||||
if (!activeWorktreeId || !worktreePath || isFolder) {
|
||||
|
||||
@@ -69,6 +69,41 @@ describe('RightSidebarEdgePeekZone', () => {
|
||||
expect(useAppStore.getState().rightSidebarPeek).toBe(false)
|
||||
})
|
||||
|
||||
it('does not arm while a mouse button is pressed at the edge', () => {
|
||||
render(<RightSidebarEdgePeekZone />)
|
||||
|
||||
act(() => {
|
||||
fireEvent.mouseMove(window, {
|
||||
clientX: RIGHT_EDGE_X,
|
||||
clientY: BELOW_TITLEBAR_Y,
|
||||
buttons: 1
|
||||
})
|
||||
})
|
||||
vi.advanceTimersByTime(PEEK_OPEN_DELAY_MS)
|
||||
|
||||
expect(useAppStore.getState().rightSidebarPeek).toBe(false)
|
||||
})
|
||||
|
||||
it('cancels an armed peek when a scrollbar drag starts', () => {
|
||||
render(<RightSidebarEdgePeekZone />)
|
||||
|
||||
moveMouse(RIGHT_EDGE_X, BELOW_TITLEBAR_Y)
|
||||
fireEvent.mouseDown(window, { buttons: 1 })
|
||||
vi.advanceTimersByTime(PEEK_OPEN_DELAY_MS)
|
||||
|
||||
expect(useAppStore.getState().rightSidebarPeek).toBe(false)
|
||||
})
|
||||
|
||||
it('cancels an armed peek when the window loses focus', () => {
|
||||
render(<RightSidebarEdgePeekZone />)
|
||||
|
||||
moveMouse(RIGHT_EDGE_X, BELOW_TITLEBAR_Y)
|
||||
fireEvent.blur(window)
|
||||
vi.advanceTimersByTime(PEEK_OPEN_DELAY_MS)
|
||||
|
||||
expect(useAppStore.getState().rightSidebarPeek).toBe(false)
|
||||
})
|
||||
|
||||
it('ignores the top titlebar zone so window controls stay clickable', () => {
|
||||
render(<RightSidebarEdgePeekZone />)
|
||||
|
||||
|
||||
@@ -55,6 +55,12 @@ export function RightSidebarEdgePeekZone(): React.JSX.Element | null {
|
||||
}
|
||||
}
|
||||
const onMouseMove = (event: MouseEvent): void => {
|
||||
// Why: scrollbar and resize drags can remain in the edge band longer
|
||||
// than the hover delay; pressed buttons must never arm an overlay.
|
||||
if (event.buttons !== 0) {
|
||||
clearOpenTimer()
|
||||
return
|
||||
}
|
||||
// Why: exclude the top 36px titlebar row so the peek never arms over
|
||||
// window controls (Windows/Linux custom chrome).
|
||||
const inEdgeBand =
|
||||
@@ -72,9 +78,13 @@ export function RightSidebarEdgePeekZone(): React.JSX.Element | null {
|
||||
}
|
||||
}
|
||||
window.addEventListener('mousemove', onMouseMove)
|
||||
window.addEventListener('mousedown', clearOpenTimer)
|
||||
window.addEventListener('blur', clearOpenTimer)
|
||||
return () => {
|
||||
clearOpenTimer()
|
||||
window.removeEventListener('mousemove', onMouseMove)
|
||||
window.removeEventListener('mousedown', clearOpenTimer)
|
||||
window.removeEventListener('blur', clearOpenTimer)
|
||||
}
|
||||
}, [edgePeekEnabled, rightSidebarOpen, rightSidebarPeek, setRightSidebarPeek])
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
import { keybindingMatchesAction } from '../../../../shared/keybindings'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { isEditableTarget } from '@/lib/editable-target'
|
||||
import { isRightSidebarRevealed } from '@/lib/right-sidebar-visibility'
|
||||
|
||||
export function shouldIgnoreFileExplorerKeyTarget(target: EventTarget | null): boolean {
|
||||
return (
|
||||
@@ -52,7 +53,7 @@ export function useFileExplorerKeys(opts: {
|
||||
scrollToIndex: (index: number) => void
|
||||
activeWorktreeId: string | null
|
||||
}): void {
|
||||
const rightSidebarOpen = useAppStore((s) => s.rightSidebarOpen)
|
||||
const rightSidebarRevealed = useAppStore(isRightSidebarRevealed)
|
||||
const rightSidebarTab = useAppStore((s) => s.rightSidebarTab)
|
||||
const rightSidebarExplorerView = useAppStore((s) => s.rightSidebarExplorerView)
|
||||
const keybindings = useAppStore((s) => s.keybindings)
|
||||
@@ -139,7 +140,7 @@ export function useFileExplorerKeys(opts: {
|
||||
|
||||
const onKeyDown = (e: KeyboardEvent): void => {
|
||||
if (
|
||||
!rightSidebarOpen ||
|
||||
!rightSidebarRevealed ||
|
||||
rightSidebarTab !== 'explorer' ||
|
||||
rightSidebarExplorerView !== 'files'
|
||||
) {
|
||||
@@ -290,5 +291,11 @@ export function useFileExplorerKeys(opts: {
|
||||
|
||||
window.addEventListener('keydown', onKeyDown, { capture: true })
|
||||
return () => window.removeEventListener('keydown', onKeyDown, { capture: true })
|
||||
}, [keybindings, rightSidebarExplorerView, rightSidebarOpen, rightSidebarTab, opts.containerRef])
|
||||
}, [
|
||||
keybindings,
|
||||
rightSidebarExplorerView,
|
||||
rightSidebarRevealed,
|
||||
rightSidebarTab,
|
||||
opts.containerRef
|
||||
])
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ type PollState = {
|
||||
gitConflictOperationByWorktree: Record<string, unknown>
|
||||
sshConnectionStates: Map<string, { status: string }>
|
||||
rightSidebarOpen?: boolean
|
||||
rightSidebarPeek?: boolean
|
||||
rightSidebarTab?: string
|
||||
rightSidebarExplorerView?: string
|
||||
openFiles?: unknown[]
|
||||
@@ -254,6 +255,27 @@ describe('useGitStatusPolling', () => {
|
||||
expect(globalThis.setInterval).toHaveBeenCalledWith(expect.any(Function), 30_000)
|
||||
})
|
||||
|
||||
it('uses the interactive git status cadence while the sidebar is peeked', async () => {
|
||||
const { gitStatus } = await usePollingOnce(
|
||||
{
|
||||
entries: [],
|
||||
conflictOperation: 'unknown',
|
||||
head: 'abc123',
|
||||
branch: 'refs/heads/main'
|
||||
},
|
||||
{
|
||||
stateOverrides: {
|
||||
rightSidebarOpen: false,
|
||||
rightSidebarPeek: true,
|
||||
openFiles: []
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
expect(gitStatus).toHaveBeenCalledTimes(1)
|
||||
expect(globalThis.setInterval).toHaveBeenCalledWith(expect.any(Function), 3000)
|
||||
})
|
||||
|
||||
it('does not install the visible git status poll while disabled', async () => {
|
||||
const { state, gitStatus } = await usePollingOnce(
|
||||
{
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import { getRightSidebarWorktreeRuntimeSettings } from './file-explorer-runtime-owner'
|
||||
import { useGitStatusFileWatchRefresh } from './git-status-file-watch-refresh'
|
||||
import { useGitStatusPushSignalRefresh } from './git-status-push-signal-refresh'
|
||||
import { isRightSidebarRevealed } from '@/lib/right-sidebar-visibility'
|
||||
|
||||
const MIN_STATUS_REFRESH_INTERVAL_MS = 3000
|
||||
const INTERACTIVE_STATUS_POLL_INTERVAL_MS = MIN_STATUS_REFRESH_INTERVAL_MS
|
||||
@@ -36,7 +37,7 @@ export function useGitStatusPolling(options: { enabled?: boolean } = {}): void {
|
||||
const setConflictOperation = useAppStore((s) => s.setConflictOperation)
|
||||
const conflictOperationByWorktree = useAppStore((s) => s.gitConflictOperationByWorktree)
|
||||
const sshConnectionStates = useAppStore((s) => s.sshConnectionStates)
|
||||
const rightSidebarOpen = useAppStore((s) => s.rightSidebarOpen)
|
||||
const rightSidebarRevealed = useAppStore(isRightSidebarRevealed)
|
||||
const rightSidebarTab = useAppStore((s) => s.rightSidebarTab)
|
||||
const rightSidebarExplorerView = useAppStore((s) => s.rightSidebarExplorerView)
|
||||
const openFiles = useAppStore((s) => s.openFiles)
|
||||
@@ -56,7 +57,7 @@ export function useGitStatusPolling(options: { enabled?: boolean } = {}): void {
|
||||
const activeGitStatusPollingArgs = {
|
||||
activeWorktreeId,
|
||||
worktreePath,
|
||||
rightSidebarOpen,
|
||||
rightSidebarOpen: rightSidebarRevealed,
|
||||
rightSidebarTab,
|
||||
rightSidebarExplorerView,
|
||||
openFiles
|
||||
@@ -179,7 +180,7 @@ export function useGitStatusPolling(options: { enabled?: boolean } = {}): void {
|
||||
isConnectionReady,
|
||||
openFiles,
|
||||
rightSidebarExplorerView,
|
||||
rightSidebarOpen,
|
||||
rightSidebarOpen: rightSidebarRevealed,
|
||||
rightSidebarTab,
|
||||
worktreePath
|
||||
})
|
||||
|
||||
@@ -59,6 +59,7 @@ describe('getEditorExternalWatchTargets', () => {
|
||||
activeWorktreeId?: string | null
|
||||
runtimeEnvironmentId?: string | null
|
||||
rightSidebarOpen?: boolean
|
||||
rightSidebarPeek?: boolean
|
||||
rightSidebarTab?: EditorExternalWatchTargetState['rightSidebarTab']
|
||||
rightSidebarExplorerView?: EditorExternalWatchTargetState['rightSidebarExplorerView']
|
||||
gitStatusHugeByWorktree?: EditorExternalWatchTargetState['gitStatusHugeByWorktree']
|
||||
@@ -69,6 +70,7 @@ describe('getEditorExternalWatchTargets', () => {
|
||||
repos: [args.repo],
|
||||
activeWorktreeId: args.activeWorktreeId ?? null,
|
||||
rightSidebarOpen: args.rightSidebarOpen ?? false,
|
||||
rightSidebarPeek: args.rightSidebarPeek ?? false,
|
||||
rightSidebarTab: args.rightSidebarTab ?? 'explorer',
|
||||
rightSidebarExplorerView: args.rightSidebarExplorerView ?? 'files',
|
||||
gitStatusHugeByWorktree: args.gitStatusHugeByWorktree ?? {},
|
||||
@@ -136,6 +138,29 @@ describe('getEditorExternalWatchTargets', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('keeps watching the active worktree while the file explorer is peeked', () => {
|
||||
const repo = makeRepo('repo-active-peeked')
|
||||
const worktree = makeWorktree(repo.id, 'wt-active-peeked')
|
||||
|
||||
expect(
|
||||
getEditorExternalWatchTargets(
|
||||
makeState({
|
||||
repo,
|
||||
worktree,
|
||||
activeWorktreeId: worktree.id,
|
||||
rightSidebarPeek: true
|
||||
})
|
||||
).targets
|
||||
).toEqual([
|
||||
{
|
||||
worktreeId: 'wt-active-peeked',
|
||||
worktreePath: '/repo-active-peeked/worktree',
|
||||
connectionId: undefined,
|
||||
runtimeEnvironmentId: null
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('does not watch the active worktree while Explorer search is visible', () => {
|
||||
const repo = makeRepo('repo-active-search')
|
||||
const worktree = makeWorktree(repo.id, 'wt-active-search')
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
} from './worktree-file-change-event'
|
||||
import { isGitRepoKind } from '../../../shared/repo-kind'
|
||||
import { markFileChangedOnDisk } from '@/components/editor/editor-changed-on-disk-mark'
|
||||
import { isRightSidebarRevealed } from '@/lib/right-sidebar-visibility'
|
||||
|
||||
// Why: atomic-write patterns (Claude Code's Edit tool, editors like vim,
|
||||
// VSCode) land as a short burst of `update` events — or `delete + create` on
|
||||
@@ -96,6 +97,7 @@ export type EditorExternalWatchTargetState = Pick<
|
||||
| 'activeWorktreeId'
|
||||
| 'settings'
|
||||
| 'rightSidebarOpen'
|
||||
| 'rightSidebarPeek'
|
||||
| 'rightSidebarTab'
|
||||
| 'rightSidebarExplorerView'
|
||||
| 'gitStatusHugeByWorktree'
|
||||
@@ -107,7 +109,7 @@ let cachedWorktreesByRepo: AppState['worktreesByRepo'] | null = null
|
||||
let cachedRepos: AppState['repos'] | null = null
|
||||
let cachedActiveWorktreeId: string | null = null
|
||||
let cachedRuntimeEnvironmentId: string | undefined
|
||||
let cachedRightSidebarOpen: boolean | null = null
|
||||
let cachedRightSidebarRevealed: boolean | null = null
|
||||
let cachedRightSidebarTab: AppState['rightSidebarTab'] | null = null
|
||||
let cachedRightSidebarExplorerView: AppState['rightSidebarExplorerView'] | null = null
|
||||
let cachedGitStatusHugeByWorktree: AppState['gitStatusHugeByWorktree'] | null = null
|
||||
@@ -129,13 +131,14 @@ export function getEditorExternalWatchTargets(
|
||||
state: EditorExternalWatchTargetState
|
||||
): WatchedTargetsSnapshot {
|
||||
const runtimeEnvironmentId = state.settings?.activeRuntimeEnvironmentId?.trim() || undefined
|
||||
const rightSidebarRevealed = isRightSidebarRevealed(state)
|
||||
if (
|
||||
cachedOpenFiles === state.openFiles &&
|
||||
cachedWorktreesByRepo === state.worktreesByRepo &&
|
||||
cachedRepos === state.repos &&
|
||||
cachedActiveWorktreeId === state.activeWorktreeId &&
|
||||
cachedRuntimeEnvironmentId === runtimeEnvironmentId &&
|
||||
cachedRightSidebarOpen === state.rightSidebarOpen &&
|
||||
cachedRightSidebarRevealed === rightSidebarRevealed &&
|
||||
cachedRightSidebarTab === state.rightSidebarTab &&
|
||||
cachedRightSidebarExplorerView === state.rightSidebarExplorerView &&
|
||||
cachedGitStatusHugeByWorktree === state.gitStatusHugeByWorktree &&
|
||||
@@ -175,7 +178,7 @@ export function getEditorExternalWatchTargets(
|
||||
state.sshConnectionStates.get(activeRepo.connectionId)?.status === 'connected')
|
||||
const activeWorktreeNeedsSidebarWatch =
|
||||
activeWorktreeId !== null &&
|
||||
state.rightSidebarOpen &&
|
||||
rightSidebarRevealed &&
|
||||
((state.rightSidebarTab === 'explorer' && state.rightSidebarExplorerView === 'files') ||
|
||||
(state.rightSidebarTab === 'source-control' && sourceControlCanConsumeWatch))
|
||||
if (activeWorktreeNeedsSidebarWatch) {
|
||||
@@ -222,7 +225,7 @@ export function getEditorExternalWatchTargets(
|
||||
cachedRepos = state.repos
|
||||
cachedActiveWorktreeId = state.activeWorktreeId
|
||||
cachedRuntimeEnvironmentId = runtimeEnvironmentId
|
||||
cachedRightSidebarOpen = state.rightSidebarOpen
|
||||
cachedRightSidebarRevealed = rightSidebarRevealed
|
||||
cachedRightSidebarTab = state.rightSidebarTab
|
||||
cachedRightSidebarExplorerView = state.rightSidebarExplorerView
|
||||
cachedGitStatusHugeByWorktree = state.gitStatusHugeByWorktree
|
||||
|
||||
@@ -123,7 +123,8 @@
|
||||
"fe21e8f6f5": "Go back ({{value0}})",
|
||||
"064bd07810": "Go back",
|
||||
"ce37cf5279": "Toggle sidebar ({{value0}})",
|
||||
"e4b9e7dff7": "Toggle sidebar"
|
||||
"e4b9e7dff7": "Toggle sidebar",
|
||||
"rightSidebarEdgePeekHint": "Hover the right edge to peek"
|
||||
},
|
||||
"web": {
|
||||
"WebConnect": {
|
||||
@@ -7120,6 +7121,10 @@
|
||||
"cardLayout": "card layout",
|
||||
"workspaceOptions": "workspace options",
|
||||
"detailed": "detailed"
|
||||
},
|
||||
"rightSidebarEdgePeek": {
|
||||
"title": "Right Sidebar Edge Peek",
|
||||
"description": "Hover the window edge to temporarily reveal a closed right sidebar."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -9540,7 +9545,8 @@
|
||||
"fc3095d2ed": "explorer",
|
||||
"aiVaultSessionHistory": "Agents",
|
||||
"folderWorkspaces": "Attached worktrees",
|
||||
"parentPrChecks": "PR Checks"
|
||||
"parentPrChecks": "PR Checks",
|
||||
"edgePeekHint": "Hover the right edge to peek"
|
||||
},
|
||||
"right": {
|
||||
"panel": {
|
||||
@@ -9925,6 +9931,14 @@
|
||||
"archivedJumpUnavailable": "This session is in an archived worktree.",
|
||||
"noActiveWorktreeMatch": "No active worktree matches this session.",
|
||||
"noActiveWorktreeTarget": "No active worktree is available."
|
||||
},
|
||||
"edge": {
|
||||
"peek": {
|
||||
"tip": {
|
||||
"title": "Hover the right edge to peek",
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -123,7 +123,8 @@
|
||||
"fe21e8f6f5": "Volver ({{value0}})",
|
||||
"064bd07810": "Volver",
|
||||
"ce37cf5279": "Alternar barra lateral ({{value0}})",
|
||||
"e4b9e7dff7": "Alternar barra lateral"
|
||||
"e4b9e7dff7": "Alternar barra lateral",
|
||||
"rightSidebarEdgePeekHint": "Hover the right edge to peek"
|
||||
},
|
||||
"web": {
|
||||
"WebConnect": {
|
||||
@@ -7083,7 +7084,11 @@
|
||||
"detailed": "detallado"
|
||||
},
|
||||
"9a115966d3": "Minimizar a la bandeja al cerrar",
|
||||
"4d5b9427b5": "Cuando está activado, cerrar la ventana mantiene Orca en ejecución en la bandeja del sistema en lugar de salir."
|
||||
"4d5b9427b5": "Cuando está activado, cerrar la ventana mantiene Orca en ejecución en la bandeja del sistema en lugar de salir.",
|
||||
"rightSidebarEdgePeek": {
|
||||
"title": "Right Sidebar Edge Peek",
|
||||
"description": "Hover the window edge to temporarily reveal a closed right sidebar."
|
||||
}
|
||||
}
|
||||
},
|
||||
"auto": {
|
||||
@@ -9540,7 +9545,8 @@
|
||||
"fc3095d2ed": "explorador",
|
||||
"aiVaultSessionHistory": "Agentes",
|
||||
"folderWorkspaces": "Worktrees adjuntos",
|
||||
"parentPrChecks": "Checks de PR"
|
||||
"parentPrChecks": "Checks de PR",
|
||||
"edgePeekHint": "Hover the right edge to peek"
|
||||
},
|
||||
"right": {
|
||||
"panel": {
|
||||
@@ -9925,6 +9931,14 @@
|
||||
"archivedJumpUnavailable": "Esta sesión está en un worktree archivado.",
|
||||
"noActiveWorktreeMatch": "Ningún worktree activo coincide con esta sesión.",
|
||||
"noActiveWorktreeTarget": "No hay ningún worktree activo disponible."
|
||||
},
|
||||
"edge": {
|
||||
"peek": {
|
||||
"tip": {
|
||||
"title": "Hover the right edge to peek",
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -123,7 +123,8 @@
|
||||
"fe21e8f6f5": "戻る ({{value0}})",
|
||||
"064bd07810": "戻る",
|
||||
"ce37cf5279": "サイドバーの切り替え ({{value0}})",
|
||||
"e4b9e7dff7": "サイドバーの切り替え"
|
||||
"e4b9e7dff7": "サイドバーの切り替え",
|
||||
"rightSidebarEdgePeekHint": "Hover the right edge to peek"
|
||||
},
|
||||
"web": {
|
||||
"WebConnect": {
|
||||
@@ -7105,7 +7106,11 @@
|
||||
"detailed": "詳細"
|
||||
},
|
||||
"9a115966d3": "閉じるときにトレイへ最小化",
|
||||
"4d5b9427b5": "有効にすると、ウィンドウを閉じてもOrcaは終了せず、システムトレイで実行を続けます。"
|
||||
"4d5b9427b5": "有効にすると、ウィンドウを閉じてもOrcaは終了せず、システムトレイで実行を続けます。",
|
||||
"rightSidebarEdgePeek": {
|
||||
"title": "Right Sidebar Edge Peek",
|
||||
"description": "Hover the window edge to temporarily reveal a closed right sidebar."
|
||||
}
|
||||
}
|
||||
},
|
||||
"auto": {
|
||||
@@ -9540,7 +9545,8 @@
|
||||
"fc3095d2ed": "エクスプローラ",
|
||||
"aiVaultSessionHistory": "エージェント",
|
||||
"folderWorkspaces": "添付ワークツリー",
|
||||
"parentPrChecks": "PR チェック"
|
||||
"parentPrChecks": "PR チェック",
|
||||
"edgePeekHint": "Hover the right edge to peek"
|
||||
},
|
||||
"right": {
|
||||
"panel": {
|
||||
@@ -9925,6 +9931,14 @@
|
||||
"archivedJumpUnavailable": "このセッションはアーカイブされたワークツリーにあります。",
|
||||
"noActiveWorktreeMatch": "このセッションに一致するアクティブなワークツリーはありません。",
|
||||
"noActiveWorktreeTarget": "利用可能なアクティブなワークツリーはありません。"
|
||||
},
|
||||
"edge": {
|
||||
"peek": {
|
||||
"tip": {
|
||||
"title": "Hover the right edge to peek",
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -123,7 +123,8 @@
|
||||
"fe21e8f6f5": "돌아가기({{value0}})",
|
||||
"064bd07810": "돌아가기",
|
||||
"ce37cf5279": "사이드바 표시/숨기기({{value0}})",
|
||||
"e4b9e7dff7": "사이드바 표시/숨기기"
|
||||
"e4b9e7dff7": "사이드바 표시/숨기기",
|
||||
"rightSidebarEdgePeekHint": "Hover the right edge to peek"
|
||||
},
|
||||
"web": {
|
||||
"WebConnect": {
|
||||
@@ -7068,6 +7069,10 @@
|
||||
"cardLayout": "카드 레이아웃",
|
||||
"workspaceOptions": "워크스페이스 옵션",
|
||||
"detailed": "상세"
|
||||
},
|
||||
"rightSidebarEdgePeek": {
|
||||
"title": "Right Sidebar Edge Peek",
|
||||
"description": "Hover the window edge to temporarily reveal a closed right sidebar."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -9540,7 +9545,8 @@
|
||||
"fc3095d2ed": "탐색기",
|
||||
"aiVaultSessionHistory": "에이전트",
|
||||
"folderWorkspaces": "연결된 워크트리",
|
||||
"parentPrChecks": "PR 검사"
|
||||
"parentPrChecks": "PR 검사",
|
||||
"edgePeekHint": "Hover the right edge to peek"
|
||||
},
|
||||
"right": {
|
||||
"panel": {
|
||||
@@ -9925,6 +9931,14 @@
|
||||
"archivedJumpUnavailable": "이 세션은 보관된 작업 트리에 있습니다.",
|
||||
"noActiveWorktreeMatch": "이 세션과 일치하는 활성 작업 트리가 없습니다.",
|
||||
"noActiveWorktreeTarget": "사용 가능한 활성 작업 트리가 없습니다."
|
||||
},
|
||||
"edge": {
|
||||
"peek": {
|
||||
"tip": {
|
||||
"title": "Hover the right edge to peek",
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -123,7 +123,8 @@
|
||||
"fe21e8f6f5": "返回 ({{value0}})",
|
||||
"064bd07810": "返回",
|
||||
"ce37cf5279": "切换侧边栏 ({{value0}})",
|
||||
"e4b9e7dff7": "切换侧边栏"
|
||||
"e4b9e7dff7": "切换侧边栏",
|
||||
"rightSidebarEdgePeekHint": "Hover the right edge to peek"
|
||||
},
|
||||
"web": {
|
||||
"WebConnect": {
|
||||
@@ -7068,7 +7069,11 @@
|
||||
"detailed": "详细"
|
||||
},
|
||||
"9a115966d3": "关闭时最小化到托盘",
|
||||
"4d5b9427b5": "启用后,关闭窗口会让 Orca 继续在系统托盘中运行,而不是退出。"
|
||||
"4d5b9427b5": "启用后,关闭窗口会让 Orca 继续在系统托盘中运行,而不是退出。",
|
||||
"rightSidebarEdgePeek": {
|
||||
"title": "Right Sidebar Edge Peek",
|
||||
"description": "Hover the window edge to temporarily reveal a closed right sidebar."
|
||||
}
|
||||
}
|
||||
},
|
||||
"auto": {
|
||||
@@ -9540,7 +9545,8 @@
|
||||
"fc3095d2ed": "资源管理器",
|
||||
"aiVaultSessionHistory": "智能体",
|
||||
"folderWorkspaces": "已附加的工作树",
|
||||
"parentPrChecks": "PR 检查"
|
||||
"parentPrChecks": "PR 检查",
|
||||
"edgePeekHint": "Hover the right edge to peek"
|
||||
},
|
||||
"right": {
|
||||
"panel": {
|
||||
@@ -9925,6 +9931,14 @@
|
||||
"archivedJumpUnavailable": "此会话位于已归档的工作树中。",
|
||||
"noActiveWorktreeMatch": "没有与此会话匹配的活动工作树。",
|
||||
"noActiveWorktreeTarget": "没有可用的活动工作树。"
|
||||
},
|
||||
"edge": {
|
||||
"peek": {
|
||||
"tip": {
|
||||
"title": "Hover the right edge to peek",
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
canShowRightSidebarForView,
|
||||
isRightSidebarRevealed,
|
||||
rightSidebarShowsPullRequestData
|
||||
} from './right-sidebar-visibility'
|
||||
import type { AppState } from '@/store/types'
|
||||
@@ -22,6 +23,7 @@ function makeState(
|
||||
}
|
||||
],
|
||||
rightSidebarOpen: true,
|
||||
rightSidebarPeek: false,
|
||||
rightSidebarTab: 'checks',
|
||||
worktreesByRepo: { 'repo-1': [{ id: 'wt-1', repoId: 'repo-1' }] },
|
||||
...overrides
|
||||
@@ -47,6 +49,12 @@ describe('right sidebar visibility helpers', () => {
|
||||
expect(canShowRightSidebarForView('terminal')).toBe(true)
|
||||
})
|
||||
|
||||
it('treats both pinned and peeked sidebars as revealed', () => {
|
||||
expect(isRightSidebarRevealed({ rightSidebarOpen: true, rightSidebarPeek: false })).toBe(true)
|
||||
expect(isRightSidebarRevealed({ rightSidebarOpen: false, rightSidebarPeek: true })).toBe(true)
|
||||
expect(isRightSidebarRevealed({ rightSidebarOpen: false, rightSidebarPeek: false })).toBe(false)
|
||||
})
|
||||
|
||||
it('does not treat hidden full-page sidebars as visible PR panels', () => {
|
||||
expect(rightSidebarShowsPullRequestData(makeState({ activeView: 'tasks' }))).toBe(false)
|
||||
})
|
||||
@@ -79,4 +87,12 @@ describe('right sidebar visibility helpers', () => {
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('detects PR panels revealed by an edge peek', () => {
|
||||
expect(
|
||||
rightSidebarShowsPullRequestData(
|
||||
makeState({ rightSidebarOpen: false, rightSidebarPeek: true })
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -17,6 +17,12 @@ export function canShowRightSidebarForView(activeView: ActiveView): boolean {
|
||||
return !RIGHT_SIDEBAR_SUPPRESSED_VIEWS.has(activeView)
|
||||
}
|
||||
|
||||
export function isRightSidebarRevealed(
|
||||
state: Pick<AppState, 'rightSidebarOpen' | 'rightSidebarPeek'>
|
||||
): boolean {
|
||||
return state.rightSidebarOpen || state.rightSidebarPeek
|
||||
}
|
||||
|
||||
export function rightSidebarShowsPullRequestData(
|
||||
state: Pick<
|
||||
AppState,
|
||||
@@ -24,13 +30,14 @@ export function rightSidebarShowsPullRequestData(
|
||||
| 'activeWorktreeId'
|
||||
| 'repos'
|
||||
| 'rightSidebarOpen'
|
||||
| 'rightSidebarPeek'
|
||||
| 'rightSidebarTab'
|
||||
| 'worktreesByRepo'
|
||||
>
|
||||
): boolean {
|
||||
if (
|
||||
!canShowRightSidebarForView(state.activeView) ||
|
||||
!state.rightSidebarOpen ||
|
||||
!isRightSidebarRevealed(state) ||
|
||||
(state.rightSidebarTab !== 'checks' && state.rightSidebarTab !== 'source-control')
|
||||
) {
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user