diff --git a/apps/desktop/src/renderer/components/layout/AppLayout.test.tsx b/apps/desktop/src/renderer/components/layout/AppLayout.test.tsx new file mode 100644 index 00000000..df969554 --- /dev/null +++ b/apps/desktop/src/renderer/components/layout/AppLayout.test.tsx @@ -0,0 +1,40 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { render, screen, act } from '@testing-library/react'; +import { AppLayout } from './AppLayout'; +import { useUIStore } from '@/stores/ui'; + +vi.mock('./TitleBar', () => ({ TitleBar: () => null })); +vi.mock('@/routes/settings', () => ({ + SettingsPage: () =>
, +})); + +describe('AppLayout', () => { + beforeEach(() => { + useUIStore.setState({ settingsOpen: false }); + }); + + it('keeps children mounted and only shows the settings overlay when open', () => { + render( + +
home
+
+ ); + + expect(screen.getByTestId('home')).toBeInTheDocument(); + expect(screen.queryByTestId('settings-overlay')).toBeNull(); + + act(() => { + useUIStore.getState().openSettings(); + }); + + // Home stays mounted underneath the overlay — the session is never torn down. + expect(screen.getByTestId('home')).toBeInTheDocument(); + expect(screen.getByTestId('settings-overlay')).toBeInTheDocument(); + + act(() => { + useUIStore.getState().closeSettings(); + }); + expect(screen.queryByTestId('settings-overlay')).toBeNull(); + expect(screen.getByTestId('home')).toBeInTheDocument(); + }); +}); diff --git a/apps/desktop/src/renderer/components/layout/AppLayout.tsx b/apps/desktop/src/renderer/components/layout/AppLayout.tsx index cb912c00..7bdb9609 100644 --- a/apps/desktop/src/renderer/components/layout/AppLayout.tsx +++ b/apps/desktop/src/renderer/components/layout/AppLayout.tsx @@ -1,15 +1,30 @@ import type { ReactNode } from 'react'; import { TitleBar } from './TitleBar'; +import { SettingsPage } from '@/routes/settings'; +import { useUIStore } from '@/stores/ui'; interface AppLayoutProps { children: ReactNode; } export function AppLayout({ children }: AppLayoutProps) { + const settingsOpen = useUIStore((s) => s.settingsOpen); + return (
-
{children}
+
+ {children} + + {/* Settings is an overlay rather than a route so opening it keeps Home + (and the active session) mounted underneath. Scoped to the content + area so the title bar / window controls stay usable. */} + {settingsOpen && ( +
+ +
+ )} +
); } diff --git a/apps/desktop/src/renderer/components/layout/TitleBar.tsx b/apps/desktop/src/renderer/components/layout/TitleBar.tsx index aba09424..edfc3170 100644 --- a/apps/desktop/src/renderer/components/layout/TitleBar.tsx +++ b/apps/desktop/src/renderer/components/layout/TitleBar.tsx @@ -3,12 +3,14 @@ import { useNavigate } from 'react-router-dom'; import { LogOut, User, ChevronDown, Settings } from 'lucide-react'; import { isElectron, getElectronAPI } from '../../lib/ipc'; import { useAuthStore } from '@/stores/auth'; +import { useUIStore } from '@/stores/ui'; export function TitleBar() { const platform = isElectron() ? getElectronAPI().platform : 'unknown'; const isMac = platform === 'darwin'; const isLinux = platform === 'linux'; const { user, logout } = useAuthStore(); + const openSettings = useUIStore((s) => s.openSettings); const navigate = useNavigate(); const [showMenu, setShowMenu] = useState(false); @@ -70,7 +72,7 @@ export function TitleBar() {