From e392f5a96b228d6ed472e89ce96904c28b751a76 Mon Sep 17 00:00:00 2001 From: Felarof Date: Tue, 17 Mar 2026 15:46:31 -0700 Subject: [PATCH 1/5] feat: convert settings page to popup dialog, move workflows to main nav Replace the dedicated settings page layout (SettingsSidebarLayout) with a modal dialog (SettingsDialog) that opens on top of the current page. Settings are now accessible via a dialog triggered from the main sidebar, eliminating the confusing dual-sidebar navigation pattern. - Create SettingsDialog with tabbed left panel and content area - Move Workflows into main sidebar navigation (feature-gated) - Remove /settings/* routes (except /settings/survey) - Delete SettingsSidebarLayout and SettingsSidebar components - Update backward compatibility redirects Co-Authored-By: Claude Opus 4.6 --- .../agent/components/sidebar/AppSidebar.tsx | 4 +- .../components/sidebar/SettingsSidebar.tsx | 182 ----------------- .../components/sidebar/SidebarNavigation.tsx | 60 +++++- .../apps/agent/entrypoints/app/App.tsx | 37 ++-- .../app/layout/SettingsSidebarLayout.tsx | 68 ------- .../entrypoints/app/layout/SidebarLayout.tsx | 26 ++- .../app/settings-dialog/SettingsDialog.tsx | 190 ++++++++++++++++++ 7 files changed, 282 insertions(+), 285 deletions(-) delete mode 100644 packages/browseros-agent/apps/agent/components/sidebar/SettingsSidebar.tsx delete mode 100644 packages/browseros-agent/apps/agent/entrypoints/app/layout/SettingsSidebarLayout.tsx create mode 100644 packages/browseros-agent/apps/agent/entrypoints/app/settings-dialog/SettingsDialog.tsx diff --git a/packages/browseros-agent/apps/agent/components/sidebar/AppSidebar.tsx b/packages/browseros-agent/apps/agent/components/sidebar/AppSidebar.tsx index 19aa522a65..29d70f37ec 100644 --- a/packages/browseros-agent/apps/agent/components/sidebar/AppSidebar.tsx +++ b/packages/browseros-agent/apps/agent/components/sidebar/AppSidebar.tsx @@ -7,11 +7,13 @@ import { SidebarUserFooter } from './SidebarUserFooter' interface AppSidebarProps { expanded?: boolean onOpenShortcuts?: () => void + onOpenSettings?: () => void } export const AppSidebar: FC = ({ expanded = false, onOpenShortcuts, + onOpenSettings, }) => { return (
= ({ )} > - + - cn( - 'flex h-9 items-center gap-2 overflow-hidden whitespace-nowrap rounded-md px-3 font-medium text-sm transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground', - isActive && 'bg-sidebar-accent text-sidebar-accent-foreground', - ) - -const getSectionClassName = (index: number) => - cn(index > 0 && 'mt-3 border-t pt-3') - -const sectionLabelClassName = - 'mb-2 px-3 font-semibold text-[10px] text-muted-foreground uppercase tracking-[0.18em]' - -const primarySettingsSections: NavSection[] = [ - { - label: 'Provider Settings', - items: [ - { name: 'BrowserOS AI', to: '/settings/ai', icon: Bot }, - { - name: 'Chat & Council Provider', - to: '/settings/chat', - icon: MessageSquare, - }, - { name: 'Search Provider', to: '/settings/search', icon: Search }, - ], - }, - { - label: 'Other', - items: [ - { - name: 'Customize BrowserOS', - to: '/settings/customization', - icon: Palette, - feature: Feature.CUSTOMIZATION_SUPPORT, - }, - { name: 'BrowserOS as MCP', to: '/settings/mcp', icon: Server }, - { - name: 'Workflows', - to: '/workflows', - icon: GitBranch, - feature: Feature.WORKFLOW_SUPPORT, - }, - ], - }, -] - -const helpItems: NavItem[] = [ - { name: 'Docs', href: 'https://docs.browseros.com/', icon: BookOpen }, - { name: 'Features', to: '/onboarding/features', icon: Compass }, - { name: 'Revisit Onboarding', to: '/onboarding', icon: RotateCcw }, -] - -export const SettingsSidebar: FC = () => { - const { supports } = useCapabilities() - - const filteredSections = primarySettingsSections - .map((section) => ({ - ...section, - items: section.items.filter( - (item) => !item.feature || supports(item.feature), - ), - })) - .filter((section) => section.items.length > 0) - - const filteredHelpItems = helpItems.filter( - (item) => !item.feature || supports(item.feature), - ) - - const renderNavItem = (item: NavItem) => { - const Icon = item.icon - - if (isExternalNavItem(item)) { - return ( - - - {item.name} - - ) - } - - return ( - getNavLinkClassName(isActive)} - > - - {item.name} - - ) - } - - const renderSection = (section: NavSection, index: number) => ( -
-
{section.label}
- -
- ) - - return ( -
-
- - - Back - - -
- -
-
- Settings -
-
{filteredSections.map(renderSection)}
-
-
Help
- -
-
-
- ) -} diff --git a/packages/browseros-agent/apps/agent/components/sidebar/SidebarNavigation.tsx b/packages/browseros-agent/apps/agent/components/sidebar/SidebarNavigation.tsx index 969154fed0..b0499909cc 100644 --- a/packages/browseros-agent/apps/agent/components/sidebar/SidebarNavigation.tsx +++ b/packages/browseros-agent/apps/agent/components/sidebar/SidebarNavigation.tsx @@ -1,6 +1,7 @@ import { Brain, CalendarClock, + GitBranch, Home, PlugZap, Settings, @@ -21,13 +22,15 @@ import { cn } from '@/lib/utils' interface SidebarNavigationProps { expanded?: boolean + onOpenSettings?: () => void } type NavItem = { name: string - to: string + to?: string icon: typeof Home feature?: Feature + action?: 'settings' } const primaryNavItems: NavItem[] = [ @@ -39,6 +42,12 @@ const primaryNavItems: NavItem[] = [ feature: Feature.MANAGED_MCP_SUPPORT, }, { name: 'Scheduled Tasks', to: '/scheduled', icon: CalendarClock }, + { + name: 'Workflows', + to: '/workflows', + icon: GitBranch, + feature: Feature.WORKFLOW_SUPPORT, + }, { name: 'Skills', to: '/home/skills', @@ -57,11 +66,15 @@ const primaryNavItems: NavItem[] = [ icon: Sparkles, feature: Feature.SOUL_SUPPORT, }, - { name: 'Settings', to: '/settings/ai', icon: Settings }, + { name: 'Settings', icon: Settings, action: 'settings' }, ] +const navItemClassName = + 'flex h-9 items-center gap-2 overflow-hidden whitespace-nowrap rounded-md px-3 font-medium text-sm transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground' + export const SidebarNavigation: FC = ({ expanded = true, + onOpenSettings, }) => { const location = useLocation() const { supports } = useCapabilities() @@ -76,16 +89,47 @@ export const SidebarNavigation: FC = ({
) @@ -99,7 +113,11 @@ export const SidebarLayout: FC = () => { onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} > - + {/* Main content - full width, centered */} @@ -113,6 +131,10 @@ export const SidebarLayout: FC = () => { open={shortcutsDialogOpen} onOpenChange={setShortcutsDialogOpen} /> + ) } diff --git a/packages/browseros-agent/apps/agent/entrypoints/app/settings-dialog/SettingsDialog.tsx b/packages/browseros-agent/apps/agent/entrypoints/app/settings-dialog/SettingsDialog.tsx new file mode 100644 index 0000000000..c5837ea1a6 --- /dev/null +++ b/packages/browseros-agent/apps/agent/entrypoints/app/settings-dialog/SettingsDialog.tsx @@ -0,0 +1,190 @@ +import { + BookOpen, + Bot, + Compass, + MessageSquare, + Palette, + RotateCcw, + Search, + Server, +} from 'lucide-react' +import { type FC, useEffect, useState } from 'react' +import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog' +import { Feature } from '@/lib/browseros/capabilities' +import { useCapabilities } from '@/lib/browseros/useCapabilities' +import { SETTINGS_PAGE_VIEWED_EVENT } from '@/lib/constants/analyticsEvents' +import { track } from '@/lib/metrics/track' +import { cn } from '@/lib/utils' +import { AISettingsPage } from '../ai-settings/AISettingsPage' +import { CustomizationPage } from '../customization/CustomizationPage' +import { LlmHubPage } from '../llm-hub/LlmHubPage' +import { MCPSettingsPage } from '../mcp-settings/MCPSettingsPage' +import { SearchProviderPage } from '../search-provider/SearchProviderPage' + +type SettingsTab = { + id: string + name: string + icon: typeof Bot + feature?: Feature + component: FC +} + +const settingsTabs: SettingsTab[] = [ + { id: 'ai', name: 'BrowserOS AI', icon: Bot, component: AISettingsPage }, + { + id: 'chat', + name: 'Chat & Council Provider', + icon: MessageSquare, + component: LlmHubPage, + }, + { + id: 'search', + name: 'Search Provider', + icon: Search, + component: SearchProviderPage, + }, + { + id: 'customization', + name: 'Customize BrowserOS', + icon: Palette, + feature: Feature.CUSTOMIZATION_SUPPORT, + component: CustomizationPage, + }, + { + id: 'mcp', + name: 'BrowserOS as MCP', + icon: Server, + component: MCPSettingsPage, + }, +] + +type HelpItem = { + name: string + icon: typeof Bot + href?: string + to?: string +} + +const helpItems: HelpItem[] = [ + { name: 'Docs', href: 'https://docs.browseros.com/', icon: BookOpen }, + { name: 'Features', to: '/onboarding/features', icon: Compass }, + { name: 'Revisit Onboarding', to: '/onboarding', icon: RotateCcw }, +] + +interface SettingsDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + defaultTab?: string +} + +export const SettingsDialog: FC = ({ + open, + onOpenChange, + defaultTab = 'ai', +}) => { + const { supports } = useCapabilities() + const [activeTab, setActiveTab] = useState(defaultTab) + + // Filter tabs by feature support + const visibleTabs = settingsTabs.filter( + (tab) => !tab.feature || supports(tab.feature), + ) + + // Track analytics on tab change + useEffect(() => { + if (open) { + track(SETTINGS_PAGE_VIEWED_EVENT, { page: `settings/${activeTab}` }) + } + }, [activeTab, open]) + + // Reset to default tab when dialog opens + useEffect(() => { + if (open) { + setActiveTab(defaultTab) + } + }, [open, defaultTab]) + + const activeTabConfig = visibleTabs.find((t) => t.id === activeTab) + const ActiveComponent = activeTabConfig?.component ?? AISettingsPage + + return ( + + + Settings +
+ {/* Left panel - tab navigation */} +
+
+ + Settings + +
+ + + {/* Help section */} + +
+ + {/* Right panel - settings content */} +
+ +
+
+
+
+ ) +} From 976f29c66613c10d0de91219c3da45f536599a72 Mon Sep 17 00:00:00 2001 From: Dani Akash Date: Wed, 18 Mar 2026 21:06:55 +0530 Subject: [PATCH 2/5] feat: setup new urls for the dialog box --- .../agent/components/sidebar/AppSidebar.tsx | 4 +- .../components/sidebar/SidebarNavigation.tsx | 11 ++- .../apps/agent/entrypoints/app/App.tsx | 66 +++++++++++--- .../entrypoints/app/layout/SidebarLayout.tsx | 26 +----- .../app/settings-dialog/SettingsDialog.tsx | 90 +++++++++++-------- .../agent/lib/settings/useOpenSettings.ts | 21 +++++ 6 files changed, 136 insertions(+), 82 deletions(-) create mode 100644 packages/browseros-agent/apps/agent/lib/settings/useOpenSettings.ts diff --git a/packages/browseros-agent/apps/agent/components/sidebar/AppSidebar.tsx b/packages/browseros-agent/apps/agent/components/sidebar/AppSidebar.tsx index 29d70f37ec..19aa522a65 100644 --- a/packages/browseros-agent/apps/agent/components/sidebar/AppSidebar.tsx +++ b/packages/browseros-agent/apps/agent/components/sidebar/AppSidebar.tsx @@ -7,13 +7,11 @@ import { SidebarUserFooter } from './SidebarUserFooter' interface AppSidebarProps { expanded?: boolean onOpenShortcuts?: () => void - onOpenSettings?: () => void } export const AppSidebar: FC = ({ expanded = false, onOpenShortcuts, - onOpenSettings, }) => { return (
= ({ )} > - + void } type NavItem = { @@ -74,9 +73,9 @@ const navItemClassName = export const SidebarNavigation: FC = ({ expanded = true, - onOpenSettings, }) => { const location = useLocation() + const navigate = useNavigate() const { supports } = useCapabilities() const filteredItems = primaryNavItems.filter( @@ -95,7 +94,11 @@ export const SidebarNavigation: FC = ({ const settingsButton = (
) @@ -113,11 +99,7 @@ export const SidebarLayout: FC = () => { onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} > - + {/* Main content - full width, centered */} @@ -131,10 +113,6 @@ export const SidebarLayout: FC = () => { open={shortcutsDialogOpen} onOpenChange={setShortcutsDialogOpen} /> - ) } diff --git a/packages/browseros-agent/apps/agent/entrypoints/app/settings-dialog/SettingsDialog.tsx b/packages/browseros-agent/apps/agent/entrypoints/app/settings-dialog/SettingsDialog.tsx index c5837ea1a6..10c01062d1 100644 --- a/packages/browseros-agent/apps/agent/entrypoints/app/settings-dialog/SettingsDialog.tsx +++ b/packages/browseros-agent/apps/agent/entrypoints/app/settings-dialog/SettingsDialog.tsx @@ -8,7 +8,14 @@ import { Search, Server, } from 'lucide-react' -import { type FC, useEffect, useState } from 'react' +import type { FC } from 'react' +import { useEffect } from 'react' +import { + type Location, + useLocation, + useNavigate, + useParams, +} from 'react-router' import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog' import { Feature } from '@/lib/browseros/capabilities' import { useCapabilities } from '@/lib/browseros/useCapabilities' @@ -71,44 +78,51 @@ const helpItems: HelpItem[] = [ { name: 'Revisit Onboarding', to: '/onboarding', icon: RotateCcw }, ] -interface SettingsDialogProps { - open: boolean - onOpenChange: (open: boolean) => void - defaultTab?: string -} - -export const SettingsDialog: FC = ({ - open, - onOpenChange, - defaultTab = 'ai', -}) => { +export const SettingsDialog: FC = () => { + const { tab } = useParams<{ tab?: string }>() + const location = useLocation() + const navigate = useNavigate() const { supports } = useCapabilities() - const [activeTab, setActiveTab] = useState(defaultTab) - // Filter tabs by feature support + const backgroundLocation = ( + location.state as { backgroundLocation?: Location } | null + )?.backgroundLocation + const visibleTabs = settingsTabs.filter( - (tab) => !tab.feature || supports(tab.feature), + (tabDef) => !tabDef.feature || supports(tabDef.feature), ) - // Track analytics on tab change - useEffect(() => { - if (open) { - track(SETTINGS_PAGE_VIEWED_EVENT, { page: `settings/${activeTab}` }) - } - }, [activeTab, open]) + const activeTab = visibleTabs.find((t) => t.id === tab) ? tab : 'ai' - // Reset to default tab when dialog opens useEffect(() => { - if (open) { - setActiveTab(defaultTab) - } - }, [open, defaultTab]) + track(SETTINGS_PAGE_VIEWED_EVENT, { page: `settings/${activeTab}` }) + }, [activeTab]) + + const handleClose = () => { + navigate(backgroundLocation?.pathname ?? '/home', { replace: true }) + } + + const handleTabChange = (tabId: string) => { + navigate(`/settings/${tabId}`, { + state: { backgroundLocation }, + replace: true, + }) + } + + const handleHelpNavigation = (to: string) => { + navigate(to, { replace: true }) + } const activeTabConfig = visibleTabs.find((t) => t.id === activeTab) const ActiveComponent = activeTabConfig?.component ?? AISettingsPage return ( - + { + if (!open) handleClose() + }} + > = ({