Context is Everything logo

Hamburger Navigation Redesign — Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Replace the horizontal tab bar and sidebar bottom action bar with two icon-based dropdown menus (hamburger + account) in the top-right corner of the main content area.

Architecture: Two new small components (AppMenu, AccountMenu) rendered inside a slim header bar in MainContent.jsx. The existing setActiveTab / panel-toggle callbacks are reused — only the trigger UI changes. The tab bar JSX block and BottomActionBar usage are removed.

Tech Stack: React, Phosphor Icons (@phosphor-icons/react), Tailwind CSS, existing useAuth hook.


File Structure

File Action Responsibility
src/components/AppMenu.jsx Create Hamburger icon + grouped dropdown popover for app navigation
src/components/AccountMenu.jsx Create Avatar icon + dropdown popover for user info, settings, sign out
src/components/MainContent.jsx Modify Add slim header with new components, remove tab bar
src/components/Sidebar.jsx Modify Remove BottomActionBar usage and logout confirmation modal
src/App.jsx Modify Destructure user from useAuth(), pass to MainContent

All paths relative to claudecodeui/.


Task 1: Create the AppMenu component

Files:

  • Create: src/components/AppMenu.jsx

  • Step 1: Create AppMenu.jsx with the dropdown component

import { useState, useRef, useEffect } from 'react';
import { List, Folder, TreeStructure, Lightbulb, Users, Wrench, ChartLineUp, Terminal, Phone } from '../components/icons';

const menuGroups = [
  {
    label: 'Content',
    items: [
      { id: 'files', label: 'Files', icon: Folder },
    ],
  },
  {
    label: 'AI / Automation',
    items: [
      { id: 'workflows', label: 'Workflows', icon: TreeStructure },
      { id: 'skills', label: 'Skills', icon: Lightbulb },
      { id: 'personas', label: 'Agents', icon: Users },
    ],
  },
  {
    label: 'Communication',
    items: [
      { id: 'meeting-room', label: 'Meeting Room', icon: Phone },
    ],
  },
  {
    label: 'System',
    items: [
      { id: 'tools', label: 'Tools', icon: Wrench },
      { id: 'reporting', label: 'Reporting', icon: ChartLineUp },
      { id: 'debug', label: 'Debug', icon: Terminal },
    ],
  },
];

export default function AppMenu({ onNavigate }) {
  const [isOpen, setIsOpen] = useState(false);
  const menuRef = useRef(null);

  useEffect(() => {
    if (!isOpen) return;
    const handleClickOutside = (e) => {
      if (menuRef.current && !menuRef.current.contains(e.target)) {
        setIsOpen(false);
      }
    };
    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, [isOpen]);

  const handleItemClick = (itemId) => {
    onNavigate(itemId);
    setIsOpen(false);
  };

  return (
    <div className="relative" ref={menuRef}>
      <button
        onClick={() => setIsOpen(!isOpen)}
        className="w-9 h-9 flex items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
        title="Menu"
      >
        <List className="w-5 h-5 text-gray-600 dark:text-gray-300" />
      </button>

      {isOpen && (
        <div className="absolute right-0 top-full mt-2 w-64 bg-white dark:bg-gray-800 rounded-xl shadow-xl border border-gray-200 dark:border-gray-700 py-1.5 z-50">
          {menuGroups.map((group, gi) => (
            <div key={group.label}>
              {gi > 0 && <div className="h-px bg-gray-100 dark:bg-gray-700 mx-3 my-1" />}
              <div className="px-3 pt-2 pb-1 text-[10px] font-semibold text-gray-400 dark:text-gray-500 uppercase tracking-wider">
                {group.label}
              </div>
              {group.items.map((item) => {
                const Icon = item.icon;
                return (
                  <button
                    key={item.id}
                    onClick={() => handleItemClick(item.id)}
                    className="w-full flex items-center gap-3 px-3 py-2.5 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg mx-0 transition-colors"
                  >
                    <Icon className="w-4 h-4 text-gray-400 dark:text-gray-500" />
                    {item.label}
                  </button>
                );
              })}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}
  • Step 2: Verify the file was created and has no syntax errors

Run: cd /Users/lindsay/Documents/work/sasha/claudecodeui && npx vite build 2>&1 | tail -5
Expected: Build succeeds (AppMenu not yet imported anywhere, so no impact)

  • Step 3: Commit
git add claudecodeui/src/components/AppMenu.jsx
git commit -m "feat(ui): add AppMenu dropdown component"

Task 2: Create the AccountMenu component

Files:

  • Create: src/components/AccountMenu.jsx

  • Step 1: Create AccountMenu.jsx with the dropdown component

import { useState, useRef, useEffect } from 'react';
import { Settings, Bug, SignOut } from '../components/icons';

export default function AccountMenu({ user, onShowSettings, onShowReportIssue, onLogout }) {
  const [isOpen, setIsOpen] = useState(false);
  const menuRef = useRef(null);

  useEffect(() => {
    if (!isOpen) return;
    const handleClickOutside = (e) => {
      if (menuRef.current && !menuRef.current.contains(e.target)) {
        setIsOpen(false);
      }
    };
    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, [isOpen]);

  const initial = user?.username?.charAt(0)?.toUpperCase() || '?';
  const displayName = user?.username || 'User';
  const role = user?.is_admin ? 'Administrator' : 'User';

  return (
    <div className="relative" ref={menuRef}>
      <button
        onClick={() => setIsOpen(!isOpen)}
        className="w-9 h-9 flex items-center justify-center rounded-full bg-purple-100 dark:bg-purple-900/40 border border-purple-200 dark:border-purple-700 hover:bg-purple-200 dark:hover:bg-purple-800/40 transition-colors text-purple-600 dark:text-purple-300 text-sm font-semibold"
        title="Account"
      >
        {initial}
      </button>

      {isOpen && (
        <div className="absolute right-0 top-full mt-2 w-60 bg-white dark:bg-gray-800 rounded-xl shadow-xl border border-gray-200 dark:border-gray-700 py-1.5 z-50">
          {/* User info */}
          <div className="flex items-center gap-3 px-4 py-3 border-b border-gray-100 dark:border-gray-700">
            <div className="w-9 h-9 rounded-full bg-purple-100 dark:bg-purple-900/40 flex items-center justify-center text-purple-600 dark:text-purple-300 text-sm font-semibold">
              {initial}
            </div>
            <div>
              <div className="text-sm font-semibold text-gray-800 dark:text-gray-100">{displayName}</div>
              <div className="text-xs text-gray-400 dark:text-gray-500">{role}</div>
            </div>
          </div>

          {/* Actions */}
          <button
            onClick={() => { onShowSettings(); setIsOpen(false); }}
            className="w-full flex items-center gap-3 px-4 py-2.5 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
          >
            <Settings className="w-4 h-4 text-gray-400 dark:text-gray-500" />
            Settings
          </button>
          <button
            onClick={() => { onShowReportIssue(); setIsOpen(false); }}
            className="w-full flex items-center gap-3 px-4 py-2.5 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
          >
            <Bug className="w-4 h-4 text-gray-400 dark:text-gray-500" />
            Report Issue
          </button>

          <div className="h-px bg-gray-100 dark:bg-gray-700 mx-3 my-1" />

          <button
            onClick={() => { onLogout(); setIsOpen(false); }}
            className="w-full flex items-center gap-3 px-4 py-2.5 text-sm text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
          >
            <SignOut className="w-4 h-4" />
            Sign Out
          </button>
        </div>
      )}
    </div>
  );
}
  • Step 2: Verify build still succeeds

Run: cd /Users/lindsay/Documents/work/sasha/claudecodeui && npx vite build 2>&1 | tail -5
Expected: Build succeeds

  • Step 3: Commit
git add claudecodeui/src/components/AccountMenu.jsx
git commit -m "feat(ui): add AccountMenu dropdown component"

Task 3: Add slim header to MainContent (alongside existing tab bar)

This task adds the new header without removing the old tab bar, so both are visible for testing.

Files:

  • Modify: src/components/MainContent.jsx

  • Step 1: Add imports for new components at the top of MainContent.jsx

Add these imports near the existing component imports (around line 5-15):

import AppMenu from './AppMenu';
import AccountMenu from './AccountMenu';
  • Step 2: Add user and onLogout to the destructured props

The MainContent component destructures props around lines 25-90. Add user and verify onShowSettings, onShowReportIssue, onLogout are already in the prop list. The prop onLogout is already passed from App.jsx (line 1612). onShowSettings is at line 1610. onShowReportIssue is at line 1611. Add user to the destructuring list.

  • Step 3: Create the onNavigate handler inside MainContent

Add this handler function inside the MainContent component, after the existing hooks/state declarations (around line 270, before the useEffect blocks):

const handleAppMenuNavigate = (itemId) => {
  switch (itemId) {
    case 'files':
      setShowFilesPanel(prev => !prev);
      break;
    case 'workflows':
      setActiveTab('chat');
      setShowWorkflowManager(true);
      break;
    case 'skills':
      setActiveTab('skills');
      break;
    case 'personas':
      setActiveTab('personas');
      break;
    case 'tools':
      setActiveTab('tools');
      break;
    case 'meeting-room':
      setShowCallBotPanel(prev => !prev);
      break;
    case 'reporting':
      window.open('/reporting', '_blank');
      break;
    case 'debug':
      setActiveTab('debug');
      break;
  }
};

This replicates the exact same onClick logic from the current tab bar buttons (lines 925, 946, 981, 994, 1007, 1025, 1038, 1045).

  • Step 4: Add the slim header bar just above the existing tab bar

Find the desktop tab bar container (around line 913). It starts with:

{/* Desktop Tab Navigation */}
<div className="hidden md:flex ...

Insert the new slim header immediately before that {/* Desktop Tab Navigation */} block:

{/* Slim Header Bar */}
<div className="hidden md:flex items-center justify-end gap-2 px-3 py-1.5 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
  <AppMenu onNavigate={handleAppMenuNavigate} />
  <AccountMenu
    user={user}
    onShowSettings={onShowSettings}
    onShowReportIssue={onShowReportIssue}
    onLogout={onLogout}
  />
</div>
  • Step 5: Verify build succeeds

Run: cd /Users/lindsay/Documents/work/sasha/claudecodeui && npx vite build 2>&1 | tail -5
Expected: Build succeeds

  • Step 6: Test in browser

Run the dev server if not already running: cd /Users/lindsay/Documents/work/sasha && ./dev.sh

Open http://localhost:3007 and verify:

  • The slim header bar appears above the existing tab bar

  • Clicking the hamburger icon opens the grouped dropdown menu

  • Clicking a menu item navigates to the correct panel (test Files, Skills, Debug at minimum)

  • Clicking the avatar opens the account dropdown with user info

  • Settings opens the settings modal

  • Click outside either menu dismisses it

  • The old tab bar still works as before

  • Step 7: Commit

git add claudecodeui/src/components/MainContent.jsx
git commit -m "feat(ui): add slim header with AppMenu and AccountMenu above tab bar"

Task 4: Pass user from App.jsx to MainContent

Files:

  • Modify: src/App.jsx

  • Step 1: Destructure user from useAuth()

In App.jsx at line 44, change:

const { logout } = useAuth();

to:

const { logout, user } = useAuth();
  • Step 2: Pass user prop to MainContent

Find the MainContent rendering (around line 1592). Add the user prop alongside the existing props:

user={user}

Add it near onLogout={logout} (line 1612) for clarity.

  • Step 3: Verify build succeeds

Run: cd /Users/lindsay/Documents/work/sasha/claudecodeui && npx vite build 2>&1 | tail -5
Expected: Build succeeds

  • Step 4: Test in browser

Open http://localhost:3007 and verify:

  • The account menu avatar shows the correct initial (e.g., "L" for Lindsay)

  • The dropdown shows the correct username and role (Administrator/User)

  • Sign Out triggers the logout flow

  • Step 5: Commit

git add claudecodeui/src/App.jsx
git commit -m "feat(ui): pass user object to MainContent for account menu"

Task 5: Remove the old tab bar

Files:

  • Modify: src/components/MainContent.jsx

  • Step 1: Remove the desktop tab bar block

Delete the entire {/* Desktop Tab Navigation */} block. This starts around line 913 (after the slim header bar we added in Task 3) and runs to approximately line 1061. It is the <div className="hidden md:flex ... block containing all the Chat, Files, Workflows, Skills, Personas, Tools, Meeting Room, Debug buttons.

The exact boundaries:

  • Starts with: {/* Desktop Tab Navigation */}
  • Ends with: the closing </div> of that flex container
  • Also remove the mobile tab section if present (lines ~1063-1173, the {/* Mobile Tab Navigation */} block that's hidden by default)

Do NOT remove the side panel rendering section (SkillsPanel, PersonasPanel, ToolsPanel, etc. at lines ~1789-1843) — those are still needed.

  • Step 2: Verify build succeeds

Run: cd /Users/lindsay/Documents/work/sasha/claudecodeui && npx vite build 2>&1 | tail -5
Expected: Build succeeds

  • Step 3: Test in browser thoroughly

Open http://localhost:3007 and verify:

  • The tab bar is gone — only the slim header with hamburger + avatar remains

  • Chat content fills more vertical space

  • All navigation still works via hamburger menu:

    • Files panel opens/closes
    • Workflows modal opens
    • Skills panel opens (side panel slides in)
    • Agents panel opens
    • Tools panel opens
    • Meeting Room panel toggles
    • Reporting opens in new tab
    • Debug shell renders
  • Returning to Chat works (click Chat in the chat interface or navigate away and back)

  • Account menu still works: Settings, Report Issue, Sign Out

  • Dark mode looks correct (toggle via settings)

  • Step 4: Commit

git add claudecodeui/src/components/MainContent.jsx
git commit -m "feat(ui): remove old horizontal tab bar, hamburger menu is now primary navigation"

Task 6: Remove BottomActionBar from Sidebar

Files:

  • Modify: src/components/Sidebar.jsx

  • Step 1: Remove the BottomActionBar import

At line 13, remove:

import BottomActionBar from './BottomActionBar';
  • Step 2: Remove the BottomActionBar container and component

At approximately line 4072, remove the container div and BottomActionBar component:

<div className="absolute bottom-4 left-4 hidden md:block z-[200] pointer-events-auto">
  <BottomActionBar
    onShowReportIssue={onShowReportIssue}
    onShowSettings={onShowSettings}
    onLogout={() => setShowLogoutConfirm(true)}
  />
</div>
  • Step 3: Move the logout confirmation modal to MainContent (or keep in Sidebar)

The logout confirmation modal is at Sidebar.jsx lines 4218-4253. Since Sign Out is now in AccountMenu, we need to decide: the onLogout prop passed to AccountMenu is the direct logout function from App.jsx (not the one that shows confirmation).

We should add a confirmation step. Update AccountMenu to show a confirmation dialog before calling onLogout. Edit src/components/AccountMenu.jsx:

Add state for the confirmation:

const [showLogoutConfirm, setShowLogoutConfirm] = useState(false);

Change the Sign Out button's onClick from:

onClick={() => { onLogout(); setIsOpen(false); }}

to:

onClick={() => { setShowLogoutConfirm(true); setIsOpen(false); }}

Add the confirmation modal at the end of the component's return, after the dropdown div closes but still inside the outer <div>:

{showLogoutConfirm && (
  <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-[9999]">
    <div className="bg-white dark:bg-gray-800 rounded-xl shadow-2xl p-6 max-w-sm mx-4 border border-gray-200 dark:border-gray-700">
      <div className="flex items-center gap-3 mb-4">
        <div className="w-10 h-10 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center">
          <SignOut className="w-5 h-5 text-red-600 dark:text-red-400" />
        </div>
        <h3 className="text-lg font-semibold text-gray-900 dark:text-white">Sign Out</h3>
      </div>
      <p className="text-sm text-gray-600 dark:text-gray-300 mb-6">
        Are you sure you want to sign out? Your current session will be saved.
      </p>
      <div className="flex gap-3 justify-end">
        <button
          onClick={() => setShowLogoutConfirm(false)}
          className="px-4 py-2 text-sm text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
        >
          Cancel
        </button>
        <button
          onClick={() => { onLogout(); setShowLogoutConfirm(false); }}
          className="px-4 py-2 text-sm text-white bg-red-600 rounded-lg hover:bg-red-700 transition-colors"
        >
          Sign Out
        </button>
      </div>
    </div>
  </div>
)}
  • Step 4: Remove the logout confirmation modal from Sidebar.jsx

Remove the showLogoutConfirm state declaration (line 336):

const [showLogoutConfirm, setShowLogoutConfirm] = useState(false);

Remove the const { logout } = useAuth(); import (line 337) if it's only used for the logout confirmation. Check if logout is used elsewhere in Sidebar — if not, remove the import.

Remove the logout confirmation modal JSX (lines ~4218-4253).

  • Step 5: Clean up unused Sidebar props

In Sidebar.jsx, check if onShowSettings and onShowReportIssue props are still used elsewhere in the component (besides the removed BottomActionBar). If they're only used by BottomActionBar, remove them from the destructured props.

In App.jsx, remove the corresponding prop values from the Sidebar component rendering (lines ~1515-1516) if no longer needed:

onShowSettings={() => setShowToolsSettings(true)}
onShowReportIssue={() => setShowReportIssue(true)}
  • Step 6: Verify build succeeds

Run: cd /Users/lindsay/Documents/work/sasha/claudecodeui && npx vite build 2>&1 | tail -5
Expected: Build succeeds

  • Step 7: Test in browser

Open http://localhost:3007 and verify:

  • Sidebar no longer shows the bottom action bar

  • Sidebar sessions list has more vertical space

  • Sign Out from account menu shows confirmation dialog

  • Confirming Sign Out logs out correctly

  • Cancelling keeps you logged in

  • Settings is accessible only from account menu (no duplicate)

  • Report Issue is accessible only from account menu

  • Step 8: Commit

git add claudecodeui/src/components/Sidebar.jsx claudecodeui/src/components/AccountMenu.jsx claudecodeui/src/App.jsx
git commit -m "feat(ui): remove bottom action bar from sidebar, add logout confirmation to account menu"

Task 7: Final cleanup

Files:

  • Possibly delete: src/components/BottomActionBar.jsx

  • Modify: src/components/MainContent.jsx (remove any dead props/imports)

  • Step 1: Check if BottomActionBar is imported anywhere else

Search for BottomActionBar across the codebase. If only imported in Sidebar.jsx (which we removed), the file can be deleted.

Run: grep -r "BottomActionBar" claudecodeui/src/

If no results, delete the file:

rm claudecodeui/src/components/BottomActionBar.jsx
  • Step 2: Remove unused imports from MainContent.jsx

After removing the tab bar, some icon imports in MainContent.jsx may be unused (e.g., Folder, TreeStructure, Lightbulb, Users, Wrench, Terminal, Phone, BookOpen if they were only used in the tab bar). Check each one — some may still be used in other parts of the component.

  • Step 3: Verify build succeeds

Run: cd /Users/lindsay/Documents/work/sasha/claudecodeui && npx vite build 2>&1 | tail -5
Expected: Build succeeds with no new warnings

  • Step 4: Final browser test

Open http://localhost:3007 and do a full walkthrough:

  • Login works

  • Chat is default view with slim header (hamburger + avatar top-right)

  • All hamburger menu items navigate correctly

  • Account menu shows correct user info

  • Settings modal opens

  • Report Issue works

  • Sign Out with confirmation works

  • Dark/light mode both look correct

  • Sidebar is clean (sessions only, no bottom bar)

  • All side panels (skills, personas, tools, workflows) open and close properly

  • Step 5: Commit

git add -A
git commit -m "chore(ui): remove unused BottomActionBar and clean up dead imports"