diff --git a/apps/desktop/src/main/controllers/GitCtr.ts b/apps/desktop/src/main/controllers/GitCtr.ts index 739ce7bbc1..eeb5e4dcfa 100644 --- a/apps/desktop/src/main/controllers/GitCtr.ts +++ b/apps/desktop/src/main/controllers/GitCtr.ts @@ -637,7 +637,7 @@ export default class GitController extends ControllerModule { async getGitWorkingTreeStatus(dirPath: string): Promise { const execFileAsync = promisify(execFile); try { - const { stdout } = await execFileAsync('git', ['status', '--porcelain', '-z'], { + const { stdout } = await execFileAsync('git', ['status', '--porcelain', '-u', '-z'], { cwd: dirPath, timeout: 5000, }); @@ -689,7 +689,7 @@ export default class GitController extends ControllerModule { const modified: string[] = []; const deleted: string[] = []; try { - const { stdout } = await execFileAsync('git', ['status', '--porcelain', '-z'], { + const { stdout } = await execFileAsync('git', ['status', '--porcelain', '-u', '-z'], { cwd: dirPath, timeout: 5000, }); @@ -830,7 +830,7 @@ export default class GitController extends ControllerModule { const entries: Entry[] = []; const submoduleDirtyEntries: Entry[] = []; try { - const { stdout } = await execFileAsync('git', ['status', '--porcelain', '-z'], { + const { stdout } = await execFileAsync('git', ['status', '--porcelain', '-u', '-z'], { cwd: dirPath, timeout: 5000, }); diff --git a/src/features/Electron/titlebar/NavigationBar.tsx b/src/features/Electron/titlebar/NavigationBar.tsx index 0cdee025ac..d96332a5f3 100644 --- a/src/features/Electron/titlebar/NavigationBar.tsx +++ b/src/features/Electron/titlebar/NavigationBar.tsx @@ -6,6 +6,7 @@ import { ArrowLeft, ArrowRight, Clock } from 'lucide-react'; import { memo, useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import ToggleLeftPanelButton from '@/features/NavPanel/ToggleLeftPanelButton'; import { useGlobalStore } from '@/store/global'; import type { GlobalState } from '@/store/global/initialState'; import { systemStatusSelectors } from '@/store/global/selectors'; @@ -13,10 +14,21 @@ import { electronStylish } from '@/styles/electron'; import { isMacOS } from '@/utils/platform'; import { useNavigationHistory } from '../navigation/useNavigationHistory'; +import { TITLE_BAR_HORIZONTAL_PADDING } from './layout'; import RecentlyViewed from './RecentlyViewed'; const isMac = isMacOS(); +// Reserve space for macOS traffic lights so the toggle sits to their right. +// Matches the popup TitleBar's MAC_TRAFFIC_LIGHT_WIDTH (80) minus the titlebar's +// own horizontal padding, which already offsets the left edge. +const MAC_TRAFFIC_LIGHT_WIDTH = 80; +const macTrafficLightPadding = MAC_TRAFFIC_LIGHT_WIDTH - TITLE_BAR_HORIZONTAL_PADDING; + +// A persistent titlebar toggle must not share the sidebar toggle's id, or it +// would create a duplicate DOM id and get caught by NavPanelDraggable's hover CSS. +const NAV_TOGGLE_ID = 'titlebar_toggle_left_panel_button'; + const navPanelSelector = (s: GlobalState) => { const showLeftPanel = systemStatusSelectors.showLeftPanel(s); if (!showLeftPanel) return 0; @@ -74,13 +86,24 @@ const NavigationBar = memo(() => { horizontal align="center" data-width={leftPanelWidth} - justify="end" + gap={8} + justify={isMac ? 'space-between' : 'end'} style={{ + paddingLeft: isMac ? macTrafficLightPadding : 0, paddingRight: 8, - width: isLeftPanelVisible ? `${leftPanelWidth - 12}px` : '150px', + // Expanded: span the sidebar width so the right group hugs its right edge. + // Collapsed (macOS): shrink to content so the controls cluster at the left edge. + width: isLeftPanelVisible ? `${leftPanelWidth - 12}px` : isMac ? 'auto' : '150px', transition: !isLeftPanelVisible ? 'width 0.2s' : 'none', }} > + {/* The persistent panel toggle is macOS-only; other platforms keep the + in-page toggles, so the titlebar shows just the navigation controls. */} + {isMac && ( + + + + )} diff --git a/src/features/NavHeader/index.tsx b/src/features/NavHeader/index.tsx index 9921ca70ae..7dc0cc4331 100644 --- a/src/features/NavHeader/index.tsx +++ b/src/features/NavHeader/index.tsx @@ -3,7 +3,7 @@ import { Flexbox, TooltipGroup } from '@lobehub/ui'; import { type CSSProperties, type ReactNode } from 'react'; import { memo } from 'react'; -import ToggleLeftPanelButton from '@/features/NavPanel/ToggleLeftPanelButton'; +import ToggleLeftPanelButton, { isMacDesktop } from '@/features/NavPanel/ToggleLeftPanelButton'; import { useGlobalStore } from '@/store/global'; import { systemStatusSelectors } from '@/store/global/selectors'; @@ -39,7 +39,9 @@ const NavHeader = memo( const noContent = !left && !right; - if (noContent && expand) return; + // When empty, this header only rendered to host the collapse toggle. Hide it + // when expanded, and also on macOS desktop where the toggle moved to the titlebar. + if (noContent && (expand || isMacDesktop)) return; return ( ( - ({ title, showActive, icon, size }) => { + ({ title, showActive, icon, size, id = TOGGLE_BUTTON_ID, forceVisible }) => { const [expand, togglePanel] = useGlobalStore((s) => [ systemStatusSelectors.showLeftPanel(s), s.toggleLeftPanel, @@ -33,11 +51,13 @@ const ToggleLeftPanelButton = memo( const { t } = useTranslation(['chat', 'hotkey']); + if (isMacDesktop && !forceVisible) return null; + return (