diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 7e32d28b..cf2e9ef1 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -14,6 +14,7 @@ import { TodoDetailView } from './pages/TodoDetailView';
import { TaskBoard } from './pages/TaskBoard';
import { ErrorBoundary } from './components/ErrorBoundary';
import { MobileShell } from './components/MobileShell';
+import { DesktopShell } from './components/DesktopShell';
import { useIsDesktop } from './hooks/useMediaQuery';
function ProtectedRoute({ children }: { children: React.ReactNode }) {
@@ -41,6 +42,12 @@ function ChatRoute() {
return isDesktop ? : ;
}
+function PageRoute({ children }: { children: React.ReactNode }) {
+ const isDesktop = useIsDesktop();
+ if (!isDesktop) return <>{children}>;
+ return ;
+}
+
function dismissKeyboard(e: React.MouseEvent | React.TouchEvent) {
const target = e.target as HTMLElement;
const tag = target.tagName;
@@ -96,7 +103,9 @@ export function App() {
path="/inbox"
element={
-
+
+
+
}
/>
@@ -104,7 +113,9 @@ export function App() {
path="/calendar"
element={
-
+
+
+
}
/>
@@ -112,7 +123,9 @@ export function App() {
path="/todos"
element={
-
+
+
+
}
/>
@@ -120,7 +133,9 @@ export function App() {
path="/todos/:id"
element={
-
+
+
+
}
/>
@@ -128,7 +143,9 @@ export function App() {
path="/tasks"
element={
-
+
+
+
}
/>
@@ -137,7 +154,9 @@ export function App() {
element={
-
+
+
+
}
diff --git a/frontend/src/components/DesktopNav.tsx b/frontend/src/components/DesktopNav.tsx
new file mode 100644
index 00000000..1eae98f2
--- /dev/null
+++ b/frontend/src/components/DesktopNav.tsx
@@ -0,0 +1,48 @@
+import { useLocation, useNavigate } from 'react-router-dom';
+import { useTabBadges } from '../hooks/useTabBadges';
+
+interface NavItem {
+ label: string;
+ path: string;
+ match: (pathname: string) => boolean;
+ badge?: number;
+}
+
+export function DesktopNav() {
+ const location = useLocation();
+ const navigate = useNavigate();
+ const { inboxCount, todoCount } = useTabBadges();
+
+ const items: NavItem[] = [
+ {
+ label: 'Chat',
+ path: '/',
+ match: (p) => p === '/' || p === '/chat' || p.startsWith('/chat/'),
+ },
+ { label: 'Calendar', path: '/calendar', match: (p) => p.startsWith('/calendar') },
+ { label: 'Inbox', path: '/inbox', match: (p) => p === '/inbox', badge: inboxCount },
+ {
+ label: 'Telos',
+ path: '/todos',
+ match: (p) => p === '/todos' || p.startsWith('/todos/'),
+ badge: todoCount,
+ },
+ { label: 'Tasks', path: '/tasks', match: (p) => p.startsWith('/tasks') },
+ { label: 'Files', path: '/files', match: (p) => p.startsWith('/files') },
+ ];
+
+ return (
+
+ );
+}
diff --git a/frontend/src/components/DesktopShell.tsx b/frontend/src/components/DesktopShell.tsx
index 229bdd08..7ac0fcd5 100644
--- a/frontend/src/components/DesktopShell.tsx
+++ b/frontend/src/components/DesktopShell.tsx
@@ -1,9 +1,10 @@
import { useState, useCallback, type ReactNode } from 'react';
+import { DesktopNav } from './DesktopNav';
export interface DesktopShellProps {
- left: ReactNode;
+ left?: ReactNode;
center: ReactNode;
- right: ReactNode;
+ right?: ReactNode;
statusBar?: ReactNode;
}
@@ -55,25 +56,32 @@ export function DesktopShell({ left, center, right, statusBar }: DesktopShellPro
- {!leftCollapsed && left}
+ {!leftCollapsed && (
+ <>
+
+ {left}
+ >
+ )}
{center}
-
-
- {!rightCollapsed && right}
-
+
+ {!rightCollapsed && right}
+
+ )}
{statusBar && {statusBar}
}
diff --git a/frontend/src/components/__tests__/DesktopShell.test.tsx b/frontend/src/components/__tests__/DesktopShell.test.tsx
index 42625613..3670b255 100644
--- a/frontend/src/components/__tests__/DesktopShell.test.tsx
+++ b/frontend/src/components/__tests__/DesktopShell.test.tsx
@@ -1,6 +1,11 @@
// @vitest-environment jsdom
-import { describe, it, expect, afterEach, beforeEach } from 'vitest';
+import { describe, it, expect, afterEach, beforeEach, vi } from 'vitest';
import { render, screen, cleanup, fireEvent } from '@testing-library/react';
+
+vi.mock('../DesktopNav', () => ({
+ DesktopNav: () => ,
+}));
+
import { DesktopShell } from '../DesktopShell';
beforeEach(() => {
@@ -31,7 +36,7 @@ describe('DesktopShell', () => {
right={Right
}
/>,
);
- const toggleBtn = screen.getByTitle('Hide sessions');
+ const toggleBtn = screen.getByTitle('Hide sidebar');
fireEvent.click(toggleBtn);
expect(screen.queryByText('Left Panel')).toBeNull();
expect(localStorage.getItem('mitzo-sidebar-left-collapsed')).toBe('1');
@@ -61,7 +66,7 @@ describe('DesktopShell', () => {
/>,
);
expect(screen.queryByText('Left Panel')).toBeNull();
- expect(screen.getByTitle('Show sessions')).toBeTruthy();
+ expect(screen.getByTitle('Show sidebar')).toBeTruthy();
});
it('expands collapsed sidebar on toggle', () => {
@@ -73,7 +78,7 @@ describe('DesktopShell', () => {
right={Right
}
/>,
);
- fireEvent.click(screen.getByTitle('Show sessions'));
+ fireEvent.click(screen.getByTitle('Show sidebar'));
expect(screen.getByText('Left Panel')).toBeTruthy();
expect(localStorage.getItem('mitzo-sidebar-left-collapsed')).toBe('0');
});
diff --git a/frontend/src/pages/DesktopChatView.tsx b/frontend/src/pages/DesktopChatView.tsx
index 11f23b5e..b520448a 100644
--- a/frontend/src/pages/DesktopChatView.tsx
+++ b/frontend/src/pages/DesktopChatView.tsx
@@ -202,7 +202,10 @@ export function DesktopChatView() {
}, []);
const handleSelectSession = useCallback((id: string) => navigate(`/chat/${id}`), [navigate]);
- const handleNewChat = useCallback(() => navigate('/chat'), [navigate]);
+ const handleNewChat = useCallback(() => {
+ storeNewSession();
+ navigate('/chat');
+ }, [navigate, storeNewSession]);
return (