diff --git a/desktop/src/components/controls/PermissionModeSelector.tsx b/desktop/src/components/controls/PermissionModeSelector.tsx index 1fb2e083a..2a56f5da0 100644 --- a/desktop/src/components/controls/PermissionModeSelector.tsx +++ b/desktop/src/components/controls/PermissionModeSelector.tsx @@ -10,6 +10,7 @@ import type { PermissionMode } from '../../types/settings' import { useMobileViewport } from '../../hooks/useMobileViewport' import { isTauriRuntime } from '../../lib/desktopRuntime' import { MobileBottomSheet } from '../shared/MobileBottomSheet' +import { getOverlayRoot } from '../../lib/overlayRoot' const MODE_ICONS: Record = { default: 'verified_user', @@ -278,7 +279,7 @@ export function PermissionModeSelector({ workDir: workDirProp, compact = false, , - document.body, + getOverlayRoot(), )} ) diff --git a/desktop/src/components/layout/AppShell.tsx b/desktop/src/components/layout/AppShell.tsx index af09547bf..256d4b02b 100644 --- a/desktop/src/components/layout/AppShell.tsx +++ b/desktop/src/components/layout/AppShell.tsx @@ -274,6 +274,7 @@ export function AppShell() { +
) } diff --git a/desktop/src/components/layout/Sidebar.tsx b/desktop/src/components/layout/Sidebar.tsx index 15f989769..7a85a5dd5 100644 --- a/desktop/src/components/layout/Sidebar.tsx +++ b/desktop/src/components/layout/Sidebar.tsx @@ -1,6 +1,7 @@ import { useEffect, useState, useCallback, useMemo, useRef } from 'react' import { useSessionStore } from '../../stores/sessionStore' import { useUIStore } from '../../stores/uiStore' +import { useSettingsStore } from '../../stores/settingsStore' import { useTranslation } from '../../i18n' import { ProjectFilter } from './ProjectFilter' import { ConfirmDialog } from '../shared/ConfirmDialog' @@ -43,8 +44,10 @@ export function Sidebar({ isMobile = false, onRequestClose }: SidebarProps) { const activeTabId = useTabStore((s) => s.activeTabId) const closeTab = useTabStore((s) => s.closeTab) const disconnectSession = useChatStore((s) => s.disconnectSession) + const uiZoom = useSettingsStore((s) => s.uiZoom) const [searchQuery, setSearchQuery] = useState('') const [contextMenu, setContextMenu] = useState<{ id: string; x: number; y: number } | null>(null) + const [rightClickedSessionId, setRightClickedSessionId] = useState(null) const [pendingDeleteSessionId, setPendingDeleteSessionId] = useState(null) const [pendingBatchDeleteSessionIds, setPendingBatchDeleteSessionIds] = useState(null) const [isBatchDeleting, setIsBatchDeleting] = useState(false) @@ -64,9 +67,13 @@ export function Sidebar({ isMobile = false, onRequestClose }: SidebarProps) { useEffect(() => { if (!contextMenu) return - const close = () => setContextMenu(null) - document.addEventListener('click', close) - return () => document.removeEventListener('click', close) + const close = (e: MouseEvent) => { + if (e.button !== 0) return + setContextMenu(null) + setRightClickedSessionId(null) + } + document.addEventListener('mousedown', close) + return () => document.removeEventListener('mousedown', close) }, [contextMenu]) const filteredSessions = useMemo(() => { @@ -100,6 +107,7 @@ export function Sidebar({ isMobile = false, onRequestClose }: SidebarProps) { e.preventDefault() if (isBatchMode) return setContextMenu({ id, x: e.clientX, y: e.clientY }) + setRightClickedSessionId(id) }, [isBatchMode]) const handleDelete = useCallback((id: string) => { @@ -529,6 +537,7 @@ export function Sidebar({ isMobile = false, onRequestClose }: SidebarProps) { ? 'sidebar-session-row--active border-transparent bg-[var(--color-sidebar-item-active)] text-[var(--color-text-primary)]' : 'sidebar-session-row--idle border-transparent text-[var(--color-text-secondary)] hover:bg-[var(--color-sidebar-item-hover)]' } + ${session.id === rightClickedSessionId ? 'ring-2 ring-[var(--color-brand)] ring-offset-1 ring-offset-[var(--color-surface)]' : ''} `} aria-pressed={isBatchMode ? selectedSessionIds.has(session.id) : undefined} > @@ -602,8 +611,9 @@ export function Sidebar({ isMobile = false, onRequestClose }: SidebarProps) { {contextMenu && (
e.stopPropagation()} className="fixed z-50 min-w-[140px] rounded-[var(--radius-md)] border border-[var(--color-border)] bg-[var(--color-surface)] py-1" - style={{ left: contextMenu.x, top: contextMenu.y, boxShadow: 'var(--shadow-dropdown)' }} + style={{ left: contextMenu.x / uiZoom, top: contextMenu.y / uiZoom, boxShadow: 'var(--shadow-dropdown)' }} >
diff --git a/desktop/src/components/shared/Modal.tsx b/desktop/src/components/shared/Modal.tsx index adaa82c59..50da9213a 100644 --- a/desktop/src/components/shared/Modal.tsx +++ b/desktop/src/components/shared/Modal.tsx @@ -1,5 +1,6 @@ import { useEffect, type ReactNode } from 'react' import { createPortal } from 'react-dom' +import { getOverlayRoot } from '../../lib/overlayRoot' type ModalProps = { open: boolean @@ -63,6 +64,6 @@ export function Modal({ open, onClose, title, children, width = 560, footer }: M )} , - document.body, + getOverlayRoot(), ) } diff --git a/desktop/src/components/shared/RepositoryLaunchControls.tsx b/desktop/src/components/shared/RepositoryLaunchControls.tsx index de78fdb7e..3ecd6af79 100644 --- a/desktop/src/components/shared/RepositoryLaunchControls.tsx +++ b/desktop/src/components/shared/RepositoryLaunchControls.tsx @@ -19,6 +19,7 @@ import { DirectoryPicker } from './DirectoryPicker' import { useMobileViewport } from '../../hooks/useMobileViewport' import { isTauriRuntime } from '../../lib/desktopRuntime' import { MobileBottomSheet } from './MobileBottomSheet' +import { getOverlayRoot } from '../../lib/overlayRoot' type Props = { workDir: string @@ -555,7 +556,7 @@ export function RepositoryLaunchControls({ })} , - document.body, + getOverlayRoot(), ) )} diff --git a/desktop/src/components/shared/Toast.tsx b/desktop/src/components/shared/Toast.tsx index a48b1735f..57da6a7a4 100644 --- a/desktop/src/components/shared/Toast.tsx +++ b/desktop/src/components/shared/Toast.tsx @@ -1,4 +1,5 @@ import { useUIStore, type Toast as ToastType } from '../../stores/uiStore' +import { createOverlayPortal } from '../../lib/overlayRoot' const typeStyles: Record = { success: 'border-l-4 border-l-[var(--color-success)]', @@ -37,11 +38,11 @@ export function ToastContainer() { if (toasts.length === 0) return null - return ( + return createOverlayPortal(
{toasts.map((toast) => ( ))} -
+ , ) -} +} \ No newline at end of file diff --git a/desktop/src/components/workspace/WorkspacePanel.tsx b/desktop/src/components/workspace/WorkspacePanel.tsx index bcc46d061..9fb746ee3 100644 --- a/desktop/src/components/workspace/WorkspacePanel.tsx +++ b/desktop/src/components/workspace/WorkspacePanel.tsx @@ -7,6 +7,7 @@ import type { WorkspaceTreeResult, } from '../../api/sessions' import { useTranslation } from '../../i18n' +import { useSettingsStore } from '../../stores/settingsStore' import { useShallow } from 'zustand/react/shallow' import { useWorkspacePanelStore, @@ -41,6 +42,8 @@ type TreeNodeProps = { onToggle: (path: string) => void onOpenFile: (path: string) => void onFileContextMenu: (event: MouseEvent, path: string) => void + rightClickedFilePath: string | null + rightClickedPreviewTabId: string | null activePath: string | null } @@ -563,6 +566,8 @@ function TreeNode({ onToggle, onOpenFile, onFileContextMenu, + rightClickedFilePath, + rightClickedPreviewTabId, activePath, }: TreeNodeProps) { const t = useTranslation() @@ -584,7 +589,7 @@ function TreeNode({ isActive ? 'bg-[var(--color-surface-selected)] shadow-[inset_0_0_0_1.5px_var(--color-border-focus)]' : 'hover:bg-[var(--color-surface-hover)]' - }`} + }${entry.path === rightClickedFilePath ? ' ring-2 ring-[var(--color-brand)] ring-offset-1' : ''}`} style={{ paddingLeft: indent }} > @@ -662,6 +667,8 @@ function TreeNode({ onToggle={onToggle} onOpenFile={onOpenFile} onFileContextMenu={onFileContextMenu} + rightClickedFilePath={rightClickedFilePath} + rightClickedPreviewTabId={rightClickedPreviewTabId} activePath={activePath} /> ))} @@ -676,7 +683,9 @@ export function WorkspacePanel({ sessionId }: WorkspacePanelProps) { const [filterQuery, setFilterQuery] = useState('') const [isViewMenuOpen, setIsViewMenuOpen] = useState(false) const [previewTabContextMenu, setPreviewTabContextMenu] = useState<{ tabId: string; x: number; y: number } | null>(null) + const [rightClickedPreviewTabId, setRightClickedPreviewTabId] = useState(null) const [fileContextMenu, setFileContextMenu] = useState<{ path: string; x: number; y: number } | null>(null) + const [rightClickedFilePath, setRightClickedFilePath] = useState(null) const width = useWorkspacePanelStore((state) => state.width) const isOpen = useWorkspacePanelStore((state) => state.isPanelOpen(sessionId)) const activeView = useWorkspacePanelStore((state) => state.getActiveView(sessionId)) @@ -703,6 +712,7 @@ export function WorkspacePanel({ sessionId }: WorkspacePanelProps) { const closePanel = useWorkspacePanelStore((state) => state.closePanel) const addWorkspaceReference = useWorkspaceChatContextStore((state) => state.addReference) const chatState = useChatStore((state) => state.sessions[sessionId]?.chatState ?? 'idle') + const uiZoom = useSettingsStore((s) => s.uiZoom) const refreshLifecycleRef = useRef({ sessionId, isOpen: false, @@ -766,6 +776,8 @@ export function WorkspacePanel({ sessionId }: WorkspacePanelProps) { const close = () => { setPreviewTabContextMenu(null) setFileContextMenu(null) + setRightClickedPreviewTabId(null) + setRightClickedFilePath(null) } document.addEventListener('click', close) return () => document.removeEventListener('click', close) @@ -824,14 +836,18 @@ export function WorkspacePanel({ sessionId }: WorkspacePanelProps) { event.preventDefault() event.stopPropagation() setFileContextMenu(null) + setRightClickedFilePath(null) setPreviewTabContextMenu({ tabId, x: event.clientX, y: event.clientY }) + setRightClickedPreviewTabId(tabId) } const handleFileContextMenu = (event: MouseEvent, path: string) => { event.preventDefault() event.stopPropagation() setPreviewTabContextMenu(null) + setRightClickedPreviewTabId(null) setFileContextMenu({ path, x: event.clientX, y: event.clientY }) + setRightClickedFilePath(path) } const handleClosePreviewTabs = (scope: WorkspacePreviewCloseScope) => { @@ -942,6 +958,8 @@ export function WorkspacePanel({ sessionId }: WorkspacePanelProps) { }} onOpenFile={handleOpenFile} onFileContextMenu={handleFileContextMenu} + rightClickedFilePath={rightClickedFilePath} + rightClickedPreviewTabId={rightClickedPreviewTabId} activePath={activeTreePath} /> ))} @@ -1039,7 +1057,7 @@ export function WorkspacePanel({ sessionId }: WorkspacePanelProps) { isActive ? 'bg-[var(--color-surface-selected)] text-[var(--color-text-primary)]' : 'text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)]' - }`} + }${tab.id === rightClickedPreviewTabId ? ' ring-2 ring-[var(--color-brand)] ring-offset-1 ring-offset-[var(--color-surface-container-lowest)]' : ''}`} >