Skip to content

feat: convert settings to popup dialog - #477

Merged
Dani Akash (DaniAkash) merged 5 commits into
mainfrom
feat/new_settings_page
Mar 18, 2026
Merged

feat: convert settings to popup dialog#477
Dani Akash (DaniAkash) merged 5 commits into
mainfrom
feat/new_settings_page

Conversation

@felarof99

Copy link
Copy Markdown
Contributor

Summary

  • Replace the dedicated settings page layout with a modal dialog that opens on top of the current page, eliminating the confusing dual-sidebar navigation
  • Move Workflows from the settings sidebar into the main sidebar as a primary nav item
  • All existing settings page components (AI, Chat, Search, MCP, Customization) are reused as-is inside the new dialog

Design

Settings are now rendered in a large dialog (SettingsDialog) with a left-side tab list and a right-side content area. The dialog is triggered from the Settings button in the main sidebar (instead of navigating to /settings/*). The SettingsSidebarLayout and SettingsSidebar components are deleted and replaced by the dialog's internal tab navigation. The /settings/survey route is preserved as a standalone route. Backward compatibility redirects for old settings URLs now point to /home.

Changes

  • Created: settings-dialog/SettingsDialog.tsx — new tabbed settings dialog
  • Modified: SidebarLayout.tsx — manages dialog open/close state
  • Modified: AppSidebar.tsx — passes onOpenSettings callback
  • Modified: SidebarNavigation.tsx — Settings is now a button; Workflows added to nav
  • Modified: App.tsx — removed settings routes, updated redirects
  • Deleted: SettingsSidebarLayout.tsx, SettingsSidebar.tsx

Test plan

  • Click Settings in the main sidebar → dialog opens with BrowserOS AI tab active
  • Switch between all settings tabs (AI, Chat, Search, Customization, MCP)
  • Verify each tab renders its content correctly
  • Close the dialog → return to previous page without navigation
  • Click Workflows in the main sidebar → navigates to /workflows
  • Verify Help links in dialog (Docs opens externally, Features/Onboarding navigate)
  • Test on mobile: hamburger menu → Settings → dialog opens
  • Visit /settings/survey directly → still works as standalone page
  • Visit old /settings/ai URL → redirects to /home

🤖 Generated with Claude Code

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 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces the dedicated settings page (with its own sidebar layout) with a modal dialog that overlays the current page, using React Router's background-location pattern. Settings tabs (AI, Chat, Search, Customization, MCP) are reused as-is inside the new SettingsDialog, and Workflows is promoted to a primary nav item. The SettingsSidebar and SettingsSidebarLayout components are deleted cleanly, and the previously-reported analytics double-fire and spurious SETTINGS_PAGE_VIEWED_EVENT tracking on every route change have both been addressed.

Key observations:

  • The background-location routing pattern in AppRoutes / SettingsDialog / useOpenSettings is implemented correctly — direct /settings/:tab deep-links are caught by SettingsRedirect and re-issued with backgroundLocation: /home, so the dialog always renders over a real page.
  • activeTab in SettingsDialog is typed string | undefined by TypeScript due to the ternary pattern, even though it is always a string at runtime — this can cause downstream type errors.
  • handleHelpNavigation in SettingsDialog uses replace: true without preserving history context, meaning users who click a help link cannot press Back to return to the page they were on before opening settings.
  • The OptionsRedirect fallback for unknown legacy /options/* paths still points to /settings/ai, resulting in an extra redirect hop and being inconsistent with the PR's stated goal of pointing backward-compat routes to /home.

Confidence Score: 3/5

  • Mostly safe to merge; the background-location modal pattern is implemented correctly, but there are two logic issues (help navigation losing history context, OptionsRedirect fallback double-hop) and one TypeScript type gap that should be addressed.
  • Core routing and dialog open/close logic is solid. The previously-reported analytics double-fire is resolved. The flagged issues are real but limited in scope: the TypeScript narrowing gap on activeTab may surface as a compile error; the replace: true in handleHelpNavigation loses back-navigation context for users clicking Docs/Onboarding links from within settings; and the OptionsRedirect fallback creates an unnecessary extra redirect hop for unknown legacy paths.
  • SettingsDialog.tsx (type narrowing + help navigation history), App.tsx (OptionsRedirect fallback)

Important Files Changed

Filename Overview
packages/browseros-agent/apps/agent/entrypoints/app/settings-dialog/SettingsDialog.tsx New tabbed settings dialog using React Router background-location pattern. Has a TypeScript narrowing issue on activeTab (always a string at runtime but typed `string
packages/browseros-agent/apps/agent/entrypoints/app/App.tsx Routing refactored to use background-location modal pattern. The OptionsRedirect fallback still points to /settings/ai (creating a two-hop redirect for unknown legacy paths) rather than /home.
packages/browseros-agent/apps/agent/components/sidebar/SidebarNavigation.tsx Settings nav item converted from NavLink to a button that opens the dialog via useOpenSettings. isSettingsActive correctly reads from the actual URL, which is /settings/* when the dialog is open. Clean refactor with no blocking issues.
packages/browseros-agent/apps/agent/lib/settings/useOpenSettings.ts Thin hook wrapping navigate with the background-location state pattern. Correct use of useCallback with proper deps. No issues.
packages/browseros-agent/apps/agent/entrypoints/app/layout/SidebarLayout.tsx Cleaned up: spurious SETTINGS_PAGE_VIEWED_EVENT tracking on every route change (previously flagged) has been removed. No issues.
packages/browseros-agent/apps/agent/components/sidebar/SettingsSidebar.tsx Deleted — replaced by the tab list inside SettingsDialog. Deletion is clean with no remaining references.
packages/browseros-agent/apps/agent/entrypoints/app/layout/SettingsSidebarLayout.tsx Deleted — settings now live entirely inside SettingsDialog. Deletion is clean.

Sequence Diagram

sequenceDiagram
    participant User
    participant Sidebar as SidebarNavigation
    participant Hook as useOpenSettings
    participant Router as HashRouter / AppRoutes
    participant BG as Background Routes (SidebarLayout)
    participant Dialog as SettingsDialog (modal overlay)

    User->>Sidebar: Click Settings button
    Sidebar->>Hook: openSettings('ai')
    Hook->>Router: navigate('/settings/ai', state={backgroundLocation: /home})
    Router->>Router: backgroundLocation = /home
    Router->>BG: <Routes location=/home> → renders SidebarLayout + current page
    Router->>Dialog: <Routes> (modal overlay) → renders SettingsDialog

    User->>Dialog: Switch tab (e.g. chat)
    Dialog->>Router: navigate('/settings/chat', state={backgroundLocation: /home}, replace)
    Router->>Dialog: re-renders with activeTab='chat'

    User->>Dialog: Click Close (or Esc)
    Dialog->>Router: navigate('/home', replace)
    Router->>BG: <Routes location=/home> → SidebarLayout + home page
    Note over Dialog: Dialog unmounts (no backgroundLocation in state)

    Note over Router: Direct deep-link /settings/ai (no backgroundLocation)
    Router->>Router: SettingsRedirect → navigate('/settings/ai', state={backgroundLocation: /home})
    Router->>Dialog: Dialog rendered with background /home
Loading

Comments Outside Diff (1)

  1. packages/browseros-agent/apps/agent/entrypoints/app/App.tsx, line 47-63 (link)

    P1 OptionsRedirect fallback creates an unintended double-redirect loop

    The fallback on line 62 redirects unknown /options/* paths to /settings/ai:

    const newPath = routeMap[path] || '/settings/ai'

    But the settings routes in App.tsx now redirect all /settings/:tab requests (without a pre-set backgroundLocation in state) through SettingsRedirect. This means every /options/* URL that hits the fallback results in a two-hop redirect chain:

    1. /options/<unknown>/settings/ai (via OptionsRedirect)
    2. /settings/ai/settings/ai with state.backgroundLocation = '/home' (via SettingsRedirect)

    While this ultimately works, the fallback path is ambiguous — it was presumably /home under the new model. Setting it to /home is more consistent with the stated intent of the backward-compat redirects, and avoids the extra hop:

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: packages/browseros-agent/apps/agent/entrypoints/app/App.tsx
    Line: 47-63
    
    Comment:
    **`OptionsRedirect` fallback creates an unintended double-redirect loop**
    
    The fallback on line 62 redirects unknown `/options/*` paths to `/settings/ai`:
    ```ts
    const newPath = routeMap[path] || '/settings/ai'
    ```
    
    But the settings routes in `App.tsx` now redirect *all* `/settings/:tab` requests (without a pre-set `backgroundLocation` in state) through `SettingsRedirect`. This means every `/options/*` URL that hits the fallback results in a two-hop redirect chain:
    
    1. `/options/<unknown>``/settings/ai` (via `OptionsRedirect`)
    2. `/settings/ai``/settings/ai` with `state.backgroundLocation = '/home'` (via `SettingsRedirect`)
    
    While this ultimately works, the fallback path is ambiguous — it was presumably `/home` under the new model. Setting it to `/home` is more consistent with the stated intent of the backward-compat redirects, and avoids the extra hop:
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: packages/browseros-agent/apps/agent/entrypoints/app/settings-dialog/SettingsDialog.tsx
Line: 96

Comment:
**`activeTab` inferred as `string | undefined`**

`tab` is typed `string | undefined`, so the ternary expression `visibleTabs.find((t) => t.id === tab) ? tab : 'ai'` yields `string | undefined` in TypeScript even though at runtime it is always a `string` (the `'ai'` branch covers the undefined case). This can bubble up as a type error wherever `activeTab` is used (e.g. the template literal on line 99, the `find` on line 125, and the equality check on line 158).

A simple fix is to assert the non-null or restructure the expression so TypeScript can narrow correctly:

```suggestion
  const activeTab = (tab && visibleTabs.find((t) => t.id === tab) ? tab : 'ai') as string
```

Or, more idiomatically:

```suggestion
  const activeTab = visibleTabs.find((t) => t.id === tab)?.id ?? 'ai'
```

Both give `activeTab: string`.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: packages/browseros-agent/apps/agent/entrypoints/app/App.tsx
Line: 47-63

Comment:
**`OptionsRedirect` fallback creates an unintended double-redirect loop**

The fallback on line 62 redirects unknown `/options/*` paths to `/settings/ai`:
```ts
const newPath = routeMap[path] || '/settings/ai'
```

But the settings routes in `App.tsx` now redirect *all* `/settings/:tab` requests (without a pre-set `backgroundLocation` in state) through `SettingsRedirect`. This means every `/options/*` URL that hits the fallback results in a two-hop redirect chain:

1. `/options/<unknown>``/settings/ai` (via `OptionsRedirect`)
2. `/settings/ai``/settings/ai` with `state.backgroundLocation = '/home'` (via `SettingsRedirect`)

While this ultimately works, the fallback path is ambiguous — it was presumably `/home` under the new model. Setting it to `/home` is more consistent with the stated intent of the backward-compat redirects, and avoids the extra hop:

```suggestion
  const newPath = routeMap[path] || '/home'
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: packages/browseros-agent/apps/agent/entrypoints/app/settings-dialog/SettingsDialog.tsx
Line: 121-123

Comment:
**Help navigation drops `backgroundLocation` and silently closes the dialog**

`handleHelpNavigation` calls `navigate(to, { replace: true })` without any state. This works because the route no longer matches `/settings/:tab?`, so the dialog unmounts — but it has a subtle side-effect: because `replace: true` is used without passing the `backgroundLocation` state forward, the history entry for the dialog is *replaced* by the onboarding page. The user loses the ability to return to the page they were on before opening settings.

If the intent is to navigate away from settings and land on the target page with no dialog, consider pushing instead of replacing, so the back button takes the user to the background page:

```suggestion
  const handleHelpNavigation = (to: string) => {
    navigate(to)
  }
```

If a clean close-then-navigate behaviour is preferred, call `handleClose()` first (which restores the background page), and then push the onboarding navigation separately — though that requires a small refactor to decouple the two steps.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "fix: address review ..."

Comment thread packages/browseros-agent/apps/agent/entrypoints/app/layout/SidebarLayout.tsx Outdated
@DaniAkash

Dani Akash (DaniAkash) commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

Improvement Plan: Modal Routes for Settings Dialog

I'm making changes to this PR and will improve the settings dialog to use React Router's modal route pattern. Here's the plan:

What stays from this PR

  • SettingsDialog.tsx component (tabbed dialog UI)
  • SettingsSidebar.tsx and SettingsSidebarLayout.tsx deletions
  • ✅ Workflows moved to main sidebar
  • ✅ Survey page as standalone route

What changes

1. Settings URLs become real routes
/settings/ai, /settings/mcp etc. will be deep-linkable URLs that render the dialog as an overlay using React Router's backgroundLocation state pattern. Currently they redirect to /home.

2. No more prop drilling
The onOpenSettings prop chain (SidebarLayout → AppSidebar → SidebarNavigation) will be replaced with direct navigate('/settings/ai', { state: { backgroundLocation: location } }) calls. Any component can open settings without plumbing.

3. Back button support
Closing the dialog navigates back in browser history. The URL reflects the current state.

4. Form state preserved on tab switch
Remove key={activeTab} from <ActiveComponent> so switching tabs doesn't unmount/remount components and lose form state.

5. Fix help item navigation
Replace <a href="#/onboarding"> (causes full reload) with proper React Router navigation.

6. useOpenSettings utility hook
Simple hook for opening settings from anywhere:

const openSettings = useOpenSettings()
openSettings('mcp')

@DaniAkash

Copy link
Copy Markdown
Contributor

Greptile (@greptileai)

@DaniAkash

Copy link
Copy Markdown
Contributor

Greptile (@greptileai)

@DaniAkash
Dani Akash (DaniAkash) merged commit 42aa0ff into main Mar 18, 2026
8 of 10 checks passed
@DaniAkash
Dani Akash (DaniAkash) deleted the feat/new_settings_page branch March 18, 2026 17:56
Nikhil (shadowfax92) added a commit that referenced this pull request Mar 19, 2026
Nikhil (shadowfax92) added a commit that referenced this pull request Mar 19, 2026
* Revert "feat: convert settings to popup dialog (#477)"

This reverts commit 42aa0ff.

* fix: address review feedback for PR #498

- Remove erroneous SETTINGS_PAGE_VIEWED_EVENT tracking from SidebarLayout
  (was firing on every non-settings page navigation)
- Fix mobile settings sidebar not closing on route change by merging
  setMobileOpen(false) into the pathname-dependent analytics useEffect

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants