From 6ce38b13dcd50c4de57c5f42b0bee5cc5ff26219 Mon Sep 17 00:00:00 2001
From: nyk <93952610+0xNyk@users.noreply.github.com>
Date: Tue, 3 Mar 2026 15:08:59 +0700
Subject: [PATCH] feat: sync side panel navigation with URL routes (#76) (#87)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Move page.tsx to [[...panel]] optional catch-all route so each panel
gets its own URL (e.g. /tasks, /agents, /settings). URL is the source
of truth — synced into Zustand via usePathname on every navigation.
Enables bookmarking, refresh persistence, deep-linking, and browser
back/forward.
---
src/app/{ => [[...panel]]}/page.tsx | 11 +++++-
src/components/dashboard/dashboard.tsx | 41 +++++++++++----------
src/components/dashboard/sidebar.tsx | 6 ++-
src/components/layout/header-bar.tsx | 11 +++---
src/components/layout/local-mode-banner.tsx | 6 ++-
src/components/layout/nav-rail.tsx | 22 ++++++-----
src/lib/navigation.ts | 15 ++++++++
7 files changed, 71 insertions(+), 41 deletions(-)
rename src/app/{ => [[...panel]]}/page.tsx (95%)
create mode 100644 src/lib/navigation.ts
diff --git a/src/app/page.tsx b/src/app/[[...panel]]/page.tsx
similarity index 95%
rename from src/app/page.tsx
rename to src/app/[[...panel]]/page.tsx
index 34ec5ed..1bdfec0 100644
--- a/src/app/page.tsx
+++ b/src/app/[[...panel]]/page.tsx
@@ -1,6 +1,7 @@
'use client'
import { useEffect, useState } from 'react'
+import { usePathname } from 'next/navigation'
import { NavRail } from '@/components/layout/nav-rail'
import { HeaderBar } from '@/components/layout/header-bar'
import { LiveFeed } from '@/components/layout/live-feed'
@@ -39,7 +40,15 @@ import { useMissionControl } from '@/store'
export default function Home() {
const { connect } = useWebSocket()
- const { activeTab, setCurrentUser, setDashboardMode, setGatewayAvailable, setSubscription, liveFeedOpen, toggleLiveFeed } = useMissionControl()
+ const { activeTab, setActiveTab, setCurrentUser, setDashboardMode, setGatewayAvailable, setSubscription, liveFeedOpen, toggleLiveFeed } = useMissionControl()
+
+ // Sync URL → Zustand activeTab
+ const pathname = usePathname()
+ const panelFromUrl = pathname === '/' ? 'overview' : pathname.slice(1)
+
+ useEffect(() => {
+ setActiveTab(panelFromUrl)
+ }, [panelFromUrl, setActiveTab])
// Connect to SSE for real-time local DB events (tasks, agents, chat, etc.)
useServerEvents()
diff --git a/src/components/dashboard/dashboard.tsx b/src/components/dashboard/dashboard.tsx
index 1f76760..38ab1a9 100644
--- a/src/components/dashboard/dashboard.tsx
+++ b/src/components/dashboard/dashboard.tsx
@@ -2,6 +2,7 @@
import { useState, useCallback } from 'react'
import { useMissionControl } from '@/store'
+import { useNavigateToPanel } from '@/lib/navigation'
import { useSmartPoll } from '@/lib/use-smart-poll'
interface DbStats {
@@ -35,8 +36,8 @@ export function Dashboard() {
logs,
agents,
tasks,
- setActiveTab,
} = useMissionControl()
+ const navigateToPanel = useNavigateToPanel()
const isLocal = dashboardMode === 'local'
const subscriptionLabel = subscription?.type
? subscription.type.charAt(0).toUpperCase() + subscription.type.slice(1)
@@ -124,7 +125,7 @@ export function Dashboard() {
{isLocal ? (
<>
-
setActiveTab('sessions')}>
+
navigateToPanel('sessions')}>
-
setActiveTab('sessions')}>
+
navigateToPanel('sessions')}>
-
setActiveTab('tokens')}>
+
navigateToPanel('tokens')}>
-
setActiveTab('tokens')}>
+
navigateToPanel('tokens')}>
) : (
<>
-
setActiveTab('history')}>
+
navigateToPanel('history')}>
-
setActiveTab('agents')}>
+
navigateToPanel('agents')}>
-
setActiveTab('tasks')}>
+
navigateToPanel('tasks')}>
-
setActiveTab('logs')}>
+
navigateToPanel('logs')}>
setActiveTab('sessions')}>
+ navigateToPanel('sessions')}>
Claude Code Stats
@@ -305,7 +306,7 @@ export function Dashboard() {
) : (
- setActiveTab('audit')}>
+
navigateToPanel('audit')}>
Security & Audit
{dbStats && dbStats.audit.loginFailures > 0 && (
@@ -513,15 +514,15 @@ export function Dashboard() {
{/* Quick Actions */}
{!isLocal && (
- } setActiveTab={setActiveTab} />
+ } onNavigate={navigateToPanel} />
)}
- } setActiveTab={setActiveTab} />
- } setActiveTab={setActiveTab} />
- } setActiveTab={setActiveTab} />
+ } onNavigate={navigateToPanel} />
+ } onNavigate={navigateToPanel} />
+ } onNavigate={navigateToPanel} />
{isLocal ? (
- } setActiveTab={setActiveTab} />
+ } onNavigate={navigateToPanel} />
) : (
- } setActiveTab={setActiveTab} />
+ } onNavigate={navigateToPanel} />
)}
@@ -616,13 +617,13 @@ function StatusBadge({ connected }: { connected: boolean }) {
)
}
-function QuickAction({ label, desc, tab, icon, setActiveTab }: {
+function QuickAction({ label, desc, tab, icon, onNavigate }: {
label: string; desc: string; tab: string; icon: React.ReactNode
- setActiveTab: (tab: string) => void
+ onNavigate: (tab: string) => void
}) {
return (