Skip to content

Conversation

@stefandevo
Copy link
Collaborator

@stefandevo stefandevo commented Jan 22, 2026

Summary

  • Add unified sidebar component with improved navigation structure
  • Implement collapsible sections for Project, Tools, and GitHub nav groups
  • Add scroll indicator when content overflows
  • Enhance mobile support with responsive sidebar behavior
  • Add version number display in footer
  • Consolidate folder structure by merging unified-sidebar into sidebar

Features

  • Collapsible sections: Project (expanded by default), Tools and GitHub (collapsed by default)
  • Scroll indicator: Bouncing chevron shows when more content is below
  • Enhanced header: Project dropdown with ChevronsUpDown icon, bold active project
  • Improved footer: Documentation/Feedback links, version display, smaller styling
  • Mobile support: Proper collapsed state with dropdown menus for sections
  • Keyboard shortcuts: ⌘ symbol formatting, 1-9/0 for project switching

Test plan

  • Verify sidebar renders correctly in expanded and collapsed states
  • Test collapsible sections expand/collapse on click
  • Verify scroll indicator appears when nav content overflows
  • Test project switching via dropdown and keyboard shortcuts (1-9, 0)
  • Verify mobile responsive behavior
  • Check version number displays correctly in footer

Summary by CodeRabbit

  • New Features

    • Project dropdown in the sidebar header for quick switching, plus keyboard shortcuts for switching
    • Collapsible navigation sections with scroll indicator and dashboard shortcut
    • Documentation and Feedback controls added to the sidebar footer and OS-aware version tag
    • Sidebar now shows running agents and lightweight shortcuts; context-menu and edit actions available for projects
  • UI/UX Enhancements

    • Adjusted collapse-toggle positioning and refined collapsed/expanded sidebar layouts

✏️ Tip: You can customize this high-level summary in your review settings.

Add new unified-sidebar component for layout improvements.
- Export UnifiedSidebar from layout components
- Update root route to use new sidebar structure
Merge the unified-sidebar implementation into the standard sidebar
folder structure. The unified sidebar becomes the canonical sidebar
with improved features including collapsible sections, scroll
indicators, and enhanced mobile support.

- Delete old sidebar.tsx
- Move unified-sidebar components to sidebar/components
- Rename UnifiedSidebar to Sidebar
- Update all imports in __root.tsx
- Remove redundant unified-sidebar folder
@coderabbitai
Copy link

coderabbitai bot commented Jan 22, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Refactors sidebar UI: adds collapsible sections, project dropdowns and keyboard switching, context menu/edit dialogs, scroll tracking, expanded footer with docs/feedback, minor positioning tweak, test selector updates, and related type/exports adjustments.

Changes

Cohort / File(s) Summary
Positioning & export
apps/ui/src/components/layout/sidebar/components/collapse-toggle-button.tsx, apps/ui/src/components/layout/sidebar/index.ts
Adjusted collapse toggle button offsets (top/right) and added Sidebar re-export.
Footer UI & props
apps/ui/src/components/layout/sidebar/components/sidebar-footer.tsx
Added hideWiki prop, OS-aware version suffix, documentation/feedback controls, Electron-aware feedback handling, expanded collapsed/expanded rendering and tooltips.
Header & project switching
apps/ui/src/components/layout/sidebar/components/sidebar-header.tsx
Reworked header API (removed onClose/onExpand; added onNewProject/onOpenFolder/onProjectContextMenu), project dropdowns, dynamic project icons, logo navigation, dropdown keyboard/context menu handling.
Navigation & sections
apps/ui/src/components/layout/sidebar/components/sidebar-navigation.tsx, apps/ui/src/components/layout/sidebar/hooks/use-navigation.ts, apps/ui/src/components/layout/sidebar/types.ts
Added collapsible sections (with defaultCollapsed), Dashboard top section, GitHub/Tools collapsible sections, onScrollStateChange callback and scroll tracking; updated NavSection type.
Sidebar container & interactions
apps/ui/src/components/layout/sidebar/sidebar.tsx
Added context menu/edit-dialog state, keyboard project switching (1–9,0), handlers for new/open project, scroll indicator UI, propagated hideWiki flag.
Root layout cleanup
apps/ui/src/routes/__root.tsx
Removed ProjectSwitcher usage and related state; always render Sidebar.
E2E tests
apps/ui/tests/features/feature-manual-review-flow.spec.ts, apps/ui/tests/projects/new-project-creation.spec.ts, apps/ui/tests/projects/open-existing-project.spec.ts
Replaced sanitized-data-testid selectors with text-based assertions against the project dropdown trigger.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User
  participant SidebarHeader
  participant Sidebar (state)
  participant Router
  participant ContentArea

  User->>SidebarHeader: Click project in dropdown / press numeric key
  SidebarHeader->>Sidebar (state): request switchProject(projectId)
  Sidebar (state)->>Router: navigate("/board", { projectId })
  Router->>ContentArea: render board for projectId
  Sidebar (state)->>SidebarHeader: update currentProject state
  SidebarHeader-->>User: close dropdown / show selected project
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Enhancement, type: feature

Poem

🐰 I hop and tweak the sidebar bright,

Projects tumble, dropdowns take flight,
Keys click-skip—one through zero,
Docs and feedback now pop like a hero,
Tiny tweaks, big UI delight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: a unified sidebar component with collapsible sections and improved user experience.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@stefandevo stefandevo added the Testers-Requested Request for others to test an enhancement or bug fix/etc. label Jan 22, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @stefandevo, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significantly revamped user interface for the application's sidebar, transforming it into a unified and highly functional navigation hub. The changes focus on enhancing user experience through collapsible sections, improved project management workflows, and better responsiveness across different screen sizes. This refactor streamlines the navigation, making it more intuitive and efficient for users to interact with projects and tools, while also improving the underlying code structure.

Highlights

  • Unified Sidebar: Introduced a new, unified sidebar component that consolidates navigation and project management, replacing the separate ProjectSwitcher component.
  • Collapsible Sections: Implemented collapsible sections for 'Project', 'Tools', and 'GitHub' navigation groups, improving organization and user focus. 'Project' is expanded by default, while 'Tools' and 'GitHub' start collapsed.
  • Enhanced Project Management: Project selection now uses a dropdown menu with keyboard shortcuts (⌘1-9, ⌘0) for quick switching, and includes direct options for creating new projects and opening existing ones.
  • Improved Footer: The sidebar footer now displays the application version, operating system, and app mode, along with direct links to documentation and feedback.
  • Responsive UX: Added a scroll indicator for overflowing content and enhanced mobile support with proper collapsed states and dropdown menus for sections.
  • Code Structure Refinement: The sidebar's internal component structure has been reorganized and consolidated into a dedicated folder, improving maintainability and clarity.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@apps/ui/src/components/layout/sidebar/components/sidebar-footer.tsx`:
- Around line 53-56: The call to getElectronAPI() inside handleFeedbackClick can
throw in SSR or non-Electron environments; update handleFeedbackClick to guard
against that by wrapping the call in a try/catch or checking existence before
using it, e.g., attempt to get the API and if it's falsy or throws, gracefully
fallback (no-op or window.open) instead of calling api.openExternalLink;
reference the handleFeedbackClick function, getElectronAPI(), and
api.openExternalLink when locating and changing the code.

In `@apps/ui/src/components/layout/sidebar/components/sidebar-header.tsx`:
- Around line 202-204: The hotkey hint in the SidebarHeader component is using a
hardcoded "⌘" which is not OS-aware; replace it by importing and using
formatShortcut (from `@/store/app-store`) to render the modifier symbol for
hotkeyLabel (e.g., call formatShortcut('Cmd' or appropriate modifier) and render
that result instead of the literal "⌘{hotkeyLabel}"). Update both occurrences
referenced (the span rendering hotkeyLabel and the similar rendering at the
other location) so they call formatShortcut with the correct modifier and
concatenate the returned symbol with hotkeyLabel.
🧹 Nitpick comments (4)
apps/ui/src/components/layout/sidebar/hooks/use-navigation.ts (1)

177-202: Consider adding a keyboard shortcut for the Dashboard item.

The Dashboard navigation item lacks a shortcut property, while other navigation items have shortcuts defined. This means users cannot navigate to the Dashboard using keyboard shortcuts.

If this is intentional (e.g., Dashboard is always accessible via the sidebar), this is fine. Otherwise, consider adding a shortcut for consistency with other navigation items.

💡 Suggested change
       // Dashboard - standalone at top
       {
         label: '',
         items: [
           {
             id: 'dashboard',
             label: 'Dashboard',
             icon: Home,
+            shortcut: shortcuts.dashboard, // Add to UseNavigationProps.shortcuts
           },
         ],
       },
apps/ui/src/components/layout/sidebar/components/sidebar-navigation.tsx (2)

102-105: Redundant ternary expression.

Both branches of the ternary produce the same value 'mt-1', making the conditional pointless.

Proposed fix
       className={cn(
         'flex-1 overflow-y-auto scrollbar-hide px-3 pb-2',
-        sidebarOpen ? 'mt-1' : 'mt-1'
+        'mt-1'
       )}

299-319: Consider using the Tooltip component for consistency.

This custom span-based tooltip differs from the Tooltip component used elsewhere in the sidebar (e.g., in the collapsed dropdown section above and in sidebar-footer.tsx). Using the same Tooltip component would ensure consistent behavior, animations, and accessibility.

apps/ui/src/components/layout/sidebar/components/sidebar-header.tsx (1)

51-56: Minor: Unnecessary optional chaining.

The function signature indicates project: Project is always defined, but line 52 uses project?.icon. Since Project is not nullable here, the optional chaining is unnecessary.

Proposed fix
   const getIconComponent = (project: Project): LucideIcon => {
-    if (project?.icon && project.icon in LucideIcons) {
+    if (project.icon && project.icon in LucideIcons) {
       return (LucideIcons as unknown as Record<string, LucideIcon>)[project.icon];
     }
     return Folder;
   };

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a unified sidebar with collapsible sections, enhanced project management features, and improved mobile responsiveness. The changes are extensive, refactoring several components to support the new design and functionality. Key improvements include dynamic project switching, integrated project creation/opening, and a scroll indicator for navigation. Overall, the changes significantly enhance the user experience and modularity of the sidebar.

Style Guide Adherence Summary

No custom style guide was provided, so the review adheres to commonly accepted best practices for TypeScript/React projects. Several minor inconsistencies in UI component usage and styling were identified, which could be improved for better maintainability and consistency.

Comment on lines +67 to +209
<div className="flex flex-col items-center py-2 px-2 gap-1">
{/* Running Agents */}
{!hideRunningAgents && (
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => navigate({ to: '/running-agents' })}
className={cn(
'relative flex items-center justify-center w-10 h-10 rounded-xl',
'transition-all duration-200 ease-out titlebar-no-drag',
isActiveRoute('running-agents')
? [
'bg-gradient-to-r from-brand-500/20 via-brand-500/15 to-brand-600/10',
'text-foreground border border-brand-500/30',
'shadow-md shadow-brand-500/10',
]
: [
'text-muted-foreground hover:text-foreground',
'hover:bg-accent/50 border border-transparent hover:border-border/40',
]
)}
data-testid="running-agents-link"
>
<Activity
className={cn(
'w-[18px] h-[18px]',
isActiveRoute('running-agents') && 'text-brand-500'
)}
/>
{runningAgentsCount > 0 && (
<span
className={cn(
'absolute -top-1 -right-1 flex items-center justify-center',
'min-w-4 h-4 px-1 text-[9px] font-bold rounded-full',
'bg-brand-500 text-white shadow-sm'
)}
>
{runningAgentsCount > 99 ? '99' : runningAgentsCount}
</span>
)}
</button>
</TooltipTrigger>
<TooltipContent side="right" sideOffset={8}>
Running Agents
{runningAgentsCount > 0 && (
<span className="ml-2 px-1.5 py-0.5 bg-brand-500 text-white rounded-full text-[10px]">
{runningAgentsCount}
</span>
)}
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}

{/* Settings */}
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => navigate({ to: '/settings' })}
className={cn(
'flex items-center justify-center w-10 h-10 rounded-xl',
'transition-all duration-200 ease-out titlebar-no-drag',
isActiveRoute('settings')
? [
'bg-gradient-to-r from-brand-500/20 via-brand-500/15 to-brand-600/10',
'text-foreground border border-brand-500/30',
'shadow-md shadow-brand-500/10',
]
: [
'text-muted-foreground hover:text-foreground',
'hover:bg-accent/50 border border-transparent hover:border-border/40',
]
)}
data-testid="settings-button"
>
<Settings
className={cn(
'w-[18px] h-[18px]',
isActiveRoute('settings') && 'text-brand-500'
)}
/>
</button>
</TooltipTrigger>
<TooltipContent side="right" sideOffset={8}>
Global Settings
<span className="ml-2 px-1.5 py-0.5 bg-muted rounded text-[10px] font-mono text-muted-foreground">
{formatShortcut(shortcuts.settings, true)}
</span>
</TooltipContent>
</Tooltip>
</TooltipProvider>

{/* Documentation */}
{!hideWiki && (
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={handleWikiClick}
className={cn(
'flex items-center justify-center w-10 h-10 rounded-xl',
'text-muted-foreground hover:text-foreground',
'hover:bg-accent/50 border border-transparent hover:border-border/40',
'transition-all duration-200 ease-out titlebar-no-drag'
)}
data-testid="documentation-button"
>
<BookOpen className="w-[18px] h-[18px]" />
</button>
</TooltipTrigger>
<TooltipContent side="right" sideOffset={8}>
Documentation
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}

{/* Feedback */}
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={handleFeedbackClick}
className={cn(
'flex items-center justify-center w-10 h-10 rounded-xl',
'text-muted-foreground hover:text-foreground',
'hover:bg-accent/50 border border-transparent hover:border-border/40',
'transition-all duration-200 ease-out titlebar-no-drag'
)}
data-testid="feedback-button"
>
<MessageSquare className="w-[18px] h-[18px]" />
</button>
</TooltipTrigger>
<TooltipContent side="right" sideOffset={8}>
Feedback
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In the collapsed state, each button (Running Agents, Settings, Documentation, Feedback) is wrapped in its own TooltipProvider. This is redundant and can impact performance slightly due to unnecessary context creation. A single TooltipProvider can wrap the entire div containing these buttons.

Consider wrapping the entire div (lines 67-209) with one TooltipProvider to optimize performance and simplify the component structure.

        <div className="flex flex-col items-center py-2 px-2 gap-1">
          <TooltipProvider delayDuration={0}>
            {/* Running Agents */}
            {!hideRunningAgents && (
              <Tooltip>
                <TooltipTrigger asChild>
                  <button
                    onClick={() => navigate({ to: '/running-agents' })}
                    className={cn(
                      'relative flex items-center justify-center w-10 h-10 rounded-xl',
                      'transition-all duration-200 ease-out titlebar-no-drag',
                      isActiveRoute('running-agents')
                        ? [
                            'bg-gradient-to-r from-brand-500/20 via-brand-500/15 to-brand-600/10',
                            'text-foreground border border-brand-500/30',
                            'shadow-md shadow-brand-500/10',
                          ]
                        : [
                            'text-muted-foreground hover:text-foreground',
                            'hover:bg-accent/50 border border-transparent hover:border-border/40',
                          ]
                    )}
                    data-testid="running-agents-link"
                  >
                    <Activity
                      className={cn(
                        'w-[18px] h-[18px]',
                        isActiveRoute('running-agents') && 'text-brand-500'
                      )}
                    />
                    {runningAgentsCount > 0 && (
                      <span
                        className={cn(
                          'absolute -top-1 -right-1 flex items-center justify-center',
                          'min-w-4 h-4 px-1 text-[9px] font-bold rounded-full',
                          'bg-brand-500 text-white shadow-sm'
                        )}
                      >
                        {runningAgentsCount > 99 ? '99' : runningAgentsCount}
                      </span>
                    )}
                  </button>
                </TooltipTrigger>
                <TooltipContent side="right" sideOffset={8}>
                  Running Agents
                  {runningAgentsCount > 0 && (
                    <span className="ml-2 px-1.5 py-0.5 bg-brand-500 text-white rounded-full text-[10px]">
                      {runningAgentsCount}
                    </span>
                  )}
                </TooltipContent>
              </Tooltip>
            )}

            {/* Settings */}
            <Tooltip>
              <TooltipTrigger asChild>
                <button
                  onClick={() => navigate({ to: '/settings' })}
                  className={cn(
                    'flex items-center justify-center w-10 h-10 rounded-xl',
                    'transition-all duration-200 ease-out titlebar-no-drag',
                    isActiveRoute('settings')
                      ? [
                          'bg-gradient-to-r from-brand-500/20 via-brand-500/15 to-brand-600/10',
                          'text-foreground border border-brand-500/30',
                          'shadow-md shadow-brand-500/10',
                        ]
                      : [
                          'text-muted-foreground hover:text-foreground',
                          'hover:bg-accent/50 border border-transparent hover:border-border/40',
                        ]
                  )}
                  data-testid="settings-button"
                >
                  <Settings
                    className={cn(
                      'w-[18px] h-[18px]',
                      isActiveRoute('settings') && 'text-brand-500'
                    )}
                  />
                </button>
              </TooltipTrigger>
              <TooltipContent side="right" sideOffset={8}>
                Global Settings
                <span className="ml-2 px-1.5 py-0.5 bg-muted rounded text-[10px] font-mono text-muted-foreground">
                  {formatShortcut(shortcuts.settings, true)}
                </span>
              </TooltipContent>
            </Tooltip>

            {/* Documentation */}
            {!hideWiki && (
              <Tooltip>
                <TooltipTrigger asChild>
                  <button
                    onClick={handleWikiClick}
                    className={cn(
                      'flex items-center justify-center w-10 h-10 rounded-xl',
                      'text-muted-foreground hover:text-foreground',
                      'hover:bg-accent/50 border border-transparent hover:border-border/40',
                      'transition-all duration-200 ease-out titlebar-no-drag'
                    )}
                    data-testid="documentation-button"
                  >
                    <BookOpen className="w-[18px] h-[18px]" />
                  </button>
                </TooltipTrigger>
                <TooltipContent side="right" sideOffset={8}>
                  Documentation
                </TooltipContent>
              </Tooltip>
            )}

            {/* Feedback */}
            <Tooltip>
              <TooltipTrigger asChild>
                <button
                  onClick={handleFeedbackClick}
                  className={cn(
                    'flex items-center justify-center w-10 h-10 rounded-xl',
                    'text-muted-foreground hover:text-foreground',
                    'hover:bg-accent/50 border border-transparent hover:border-border/40',
                    'transition-all duration-200 ease-out titlebar-no-drag'
                  )}
                  data-testid="feedback-button"
                >
                  <MessageSquare className="w-[18px] h-[18px]" />
                </button>
              </TooltipTrigger>
              <TooltipContent side="right" sideOffset={8}>
                Feedback
              </TooltipContent>
            </Tooltip>
          </TooltipProvider>
        </div>

Comment on lines +314 to 348
{!hideWiki && (
<div className="px-3 py-0.5">
<button
onClick={handleWikiClick}
className={cn(
'group flex items-center w-full px-3 py-1.5 rounded-md titlebar-no-drag',
'text-muted-foreground/70 hover:text-foreground',
'hover:bg-accent/30',
'transition-all duration-200 ease-out'
)}
data-testid="documentation-button"
>
<BookOpen className="w-4 h-4 shrink-0" />
<span className="ml-2.5 text-xs">Documentation</span>
</button>
</div>
)}

{/* Feedback Link */}
<div className="px-3 pt-0.5">
<button
onClick={handleFeedbackClick}
className={cn(
'group flex items-center w-full px-3 py-1.5 rounded-md titlebar-no-drag',
'text-muted-foreground/70 hover:text-foreground',
'hover:bg-accent/30',
'transition-all duration-200 ease-out'
)}
data-testid="feedback-button"
>
<MessageSquare className="w-4 h-4 shrink-0" />
<span className="ml-2.5 text-xs">Feedback</span>
<ExternalLink className="w-3 h-3 ml-auto text-muted-foreground/50" />
</button>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The padding for the "Documentation Link" (py-0.5) and "Feedback Link" (pt-0.5) in the expanded state is inconsistent. For better visual alignment and consistency, consider using the same vertical padding for both, e.g., py-0.5 for both or py-1.5 for both if that's the desired spacing.

      {/* Documentation Link */}
      {!hideWiki && (
        <div className="px-3 py-0.5">
          <button
            onClick={handleWikiClick}
            className={cn(
              'group flex items-center w-full px-3 py-1.5 rounded-md titlebar-no-drag',
              'text-muted-foreground/70 hover:text-foreground',
              'hover:bg-accent/30',
              'transition-all duration-200 ease-out'
            )}
            data-testid="documentation-button"
          >
            <BookOpen className="w-4 h-4 shrink-0" />
            <span className="ml-2.5 text-xs">Documentation</span>
          </button>
        </div>
      )}

      {/* Feedback Link */}
      <div className="px-3 py-1.5">
        <button
          onClick={handleFeedbackClick}
          className={cn(
            'group flex items-center w-full px-3 py-1.5 rounded-md titlebar-no-drag',
            'text-muted-foreground/70 hover:text-foreground',
            'hover:bg-accent/30',
            'transition-all duration-200 ease-out'
          )}
          data-testid="feedback-button"
        >
          <MessageSquare className="w-4 h-4 shrink-0" />
          <span className="ml-2.5 text-xs">Feedback</span>
          <ExternalLink className="w-3 h-3 ml-auto text-muted-foreground/50" />
        </button>
      </div>

Comment on lines 94 to 234
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={handleLogoClick}
className="group flex flex-col items-center"
data-testid="logo-button"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 256 256"
role="img"
aria-label="Automaker Logo"
className="size-8 group-hover:rotate-12 transition-transform duration-300 ease-out"
>
<defs>
<linearGradient
id="bg-collapsed"
x1="0"
y1="0"
x2="256"
y2="256"
gradientUnits="userSpaceOnUse"
>
<stop offset="0%" style={{ stopColor: 'var(--brand-400)' }} />
<stop offset="100%" style={{ stopColor: 'var(--brand-600)' }} />
</linearGradient>
</defs>
<rect x="16" y="16" width="224" height="224" rx="56" fill="url(#bg-collapsed)" />
<g
fill="none"
stroke="#FFFFFF"
strokeWidth="20"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M92 92 L52 128 L92 164" />
<path d="M144 72 L116 184" />
<path d="M164 92 L204 128 L164 164" />
</g>
</svg>
</button>
</TooltipTrigger>
<TooltipContent side="right" sideOffset={8}>
Go to Dashboard
</TooltipContent>
</Tooltip>
</TooltipProvider>

{/* Collapsed project icon with dropdown */}
{currentProject && (
<>
<div className="w-full h-px bg-border/40 my-2" />
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>
<button
onContextMenu={(e) => onProjectContextMenu(currentProject, e)}
className="p-1 rounded-lg hover:bg-accent/50 transition-colors"
data-testid="collapsed-project-button"
>
{renderProjectIcon(currentProject)}
</button>
</DropdownMenuTrigger>
</TooltipTrigger>
<TooltipContent side="right" sideOffset={8}>
{currentProject.name}
</TooltipContent>
</Tooltip>
</TooltipProvider>
<DropdownMenuContent
align="start"
side="right"
sideOffset={8}
className="w-64"
data-testid="collapsed-project-dropdown-content"
>
<div className="px-2 py-1.5">
<span className="text-xs font-medium text-muted-foreground">Projects</span>
</div>
{projects.map((project, index) => {
const isActive = currentProject?.id === project.id;
const hotkeyLabel = index < 9 ? `${index + 1}` : index === 9 ? '0' : undefined;

return (
<DropdownMenuItem
key={project.id}
onClick={() => handleProjectSelect(project)}
onContextMenu={(e) => {
e.preventDefault();
e.stopPropagation();
setDropdownOpen(false);
onProjectContextMenu(project, e);
}}
className="flex items-center gap-3 cursor-pointer"
data-testid={`collapsed-project-item-${project.id}`}
>
{renderProjectIcon(project, 'sm')}
<span
className={cn(
'flex-1 truncate',
isActive && 'font-semibold text-foreground'
)}
>
{project.name}
</span>
{hotkeyLabel && (
<span className="text-xs text-muted-foreground">{hotkeyLabel}</span>
)}
</DropdownMenuItem>
);
})}
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => {
setDropdownOpen(false);
onNewProject();
}}
className="cursor-pointer"
data-testid="collapsed-new-project-dropdown-item"
>
<Plus className="w-4 h-4 mr-2" />
<span>New Project</span>
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
setDropdownOpen(false);
onOpenFolder();
}}
className="cursor-pointer"
data-testid="collapsed-open-project-dropdown-item"
>
<FolderOpen className="w-4 h-4 mr-2" />
<span>Open Project</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</>
)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the sidebar footer, the TooltipProvider is unnecessarily duplicated around the Tooltip components within the collapsed state's project section. A single TooltipProvider can wrap the entire div containing the DropdownMenu for better performance and cleaner code.

        <TooltipProvider delayDuration={0}>
          <Tooltip>
            <TooltipTrigger asChild>
              <button
                onClick={handleLogoClick}
                className="group flex flex-col items-center"
                data-testid="logo-button"
              >
                <svg
                  xmlns="http://www.w3.org/2000/svg"
                  viewBox="0 0 256 256"
                  role="img"
                  aria-label="Automaker Logo"
                  className="size-8 group-hover:rotate-12 transition-transform duration-300 ease-out"
                >
                  <defs>
                    <linearGradient
                      id="bg-collapsed"
                      x1="0"
                      y1="0"
                      x2="256"
                      y2="256"
                      gradientUnits="userSpaceOnUse"
                    >
                      <stop offset="0%" style={{ stopColor: 'var(--brand-400)' }} />
                      <stop offset="100%" style={{ stopColor: 'var(--brand-600)' }} />
                    </linearGradient>
                  </defs>
                  <rect x="16" y="16" width="224" height="224" rx="56" fill="url(#bg-collapsed)" />
                  <g
                    fill="none"
                    stroke="#FFFFFF"
                    strokeWidth="20"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                  >
                    <path d="M92 92 L52 128 L92 164" />
                    <path d="M144 72 L116 184" />
                    <path d="M164 92 L204 128 L164 164" />
                  </g>
                </svg>
              </button>
            </TooltipTrigger>
            <TooltipContent side="right" sideOffset={8}>
              Go to Dashboard
            </TooltipContent>
          </Tooltip>

          {/* Collapsed project icon with dropdown */}
          {currentProject && (
            <>
              <div className="w-full h-px bg-border/40 my-2" />
              <DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
                <Tooltip>
                  <TooltipTrigger asChild>
                    <DropdownMenuTrigger asChild>
                      <button
                        onContextMenu={(e) => onProjectContextMenu(currentProject, e)}
                        className="p-1 rounded-lg hover:bg-accent/50 transition-colors"
                        data-testid="collapsed-project-button"
                      >
                        {renderProjectIcon(currentProject)}
                      </button>
                    </DropdownMenuTrigger>
                  </TooltipTrigger>
                  <TooltipContent side="right" sideOffset={8}>
                    {currentProject.name}
                  </TooltipContent>
                </Tooltip>
                <DropdownMenuContent
                  align="start"
                  side="right"
                  sideOffset={8}
                  className="w-64"
                  data-testid="collapsed-project-dropdown-content"
                >
                  <div className="px-2 py-1.5">
                    <span className="text-xs font-medium text-muted-foreground">Projects</span>
                  </div>
                  {projects.map((project, index) => {
                    const isActive = currentProject?.id === project.id;
                    const hotkeyLabel = index < 9 ? `${index + 1}` : index === 9 ? '0' : undefined;

                    return (
                      <DropdownMenuItem
                        key={project.id}
                        onClick={() => handleProjectSelect(project)}
                        onContextMenu={(e) => {
                          e.preventDefault();
                          e.stopPropagation();
                          setDropdownOpen(false);
                          onProjectContextMenu(project, e);
                        }}
                        className="flex items-center gap-3 cursor-pointer"
                        data-testid={`collapsed-project-item-${project.id}`}
                      >
                        {renderProjectIcon(project, 'sm')}
                        <span
                          className={cn(
                            'flex-1 truncate',
                            isActive && 'font-semibold text-foreground'
                          )}
                        >
                          {project.name}
                        </span>
                        {hotkeyLabel && (
                          <span className="text-xs text-muted-foreground">⌘{hotkeyLabel}</span>
                        )}
                      </DropdownMenuItem>
                    );
                  })}
                  <DropdownMenuSeparator />
                  <DropdownMenuItem
                    onClick={() => {
                      setDropdownOpen(false);
                      onNewProject();
                    }}
                    className="cursor-pointer"
                    data-testid="collapsed-new-project-dropdown-item"
                  >
                    <Plus className="w-4 h-4 mr-2" />
                    <span>New Project</span>
                  </DropdownMenuItem>
                  <DropdownMenuItem
                    onClick={() => {
                      setDropdownOpen(false);
                      onOpenFolder();
                    }}
                    className="cursor-pointer"
                    data-testid="collapsed-open-project-dropdown-item"
                  >
                    <FolderOpen className="w-4 h-4 mr-2" />
                    <span>Open Project</span>
                  </DropdownMenuItem>
                </DropdownMenuContent>
              </DropdownMenu>
            </>
          )}
        </TooltipProvider>

Comment on lines +141 to +185
{section.label && !sidebarOpen && SectionIcon && section.collapsible && isCollapsed && (
<DropdownMenu>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>
<button
className={cn(
'group flex items-center justify-center w-full py-2 rounded-lg',
'text-muted-foreground hover:text-foreground',
'hover:bg-accent/50 border border-transparent hover:border-border/40',
'transition-all duration-200 ease-out'
)}
>
<SectionIcon className="w-[18px] h-[18px]" />
</button>
</DropdownMenuTrigger>
</TooltipTrigger>
<TooltipContent side="right" sideOffset={8}>
{section.label}
</TooltipContent>
</Tooltip>
</TooltipProvider>
<DropdownMenuContent side="right" align="start" sideOffset={8} className="w-48">
{section.items.map((item) => {
const ItemIcon = item.icon;
return (
<DropdownMenuItem
key={item.id}
onClick={() => navigate({ to: `/${item.id}` as unknown as '/' })}
className="flex items-center gap-2 cursor-pointer"
>
<ItemIcon className="w-4 h-4" />
<span>{item.label}</span>
{item.shortcut && (
<span className="ml-auto text-[10px] font-mono text-muted-foreground">
{formatShortcut(item.shortcut, true)}
</span>
)}
</DropdownMenuItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In the collapsed state, the DropdownMenu for section icons is wrapped in its own TooltipProvider. For better performance and cleaner code, a single TooltipProvider can wrap the entire DropdownMenu component.

            {/* Section icon with dropdown (collapsed sidebar) */}
            {section.label && !sidebarOpen && SectionIcon && section.collapsible && isCollapsed && (
              <TooltipProvider delayDuration={0}>
                <DropdownMenu>
                  <Tooltip>
                    <TooltipTrigger asChild>
                      <DropdownMenuTrigger asChild>
                        <button
                          className={cn(
                            'group flex items-center justify-center w-full py-2 rounded-lg',
                            'text-muted-foreground hover:text-foreground',
                            'hover:bg-accent/50 border border-transparent hover:border-border/40',
                            'transition-all duration-200 ease-out'
                          )}
                        >
                          <SectionIcon className="w-[18px] h-[18px]" />
                        </button>
                      </DropdownMenuTrigger>
                    </TooltipTrigger>
                    <TooltipContent side="right" sideOffset={8}>
                      {section.label}
                    </TooltipContent>
                  </Tooltip>
                  <DropdownMenuContent side="right" align="start" sideOffset={8} className="w-48">
                    {section.items.map((item) => {
                      const ItemIcon = item.icon;
                      return (
                        <DropdownMenuItem
                          key={item.id}
                          onClick={() => navigate({ to: `/${item.id}` as unknown as '/' })}
                          className="flex items-center gap-2 cursor-pointer"
                        >
                          <ItemIcon className="w-4 h-4" />
                          <span>{item.label}</span>
                          {item.shortcut && (
                            <span className="ml-auto text-[10px] font-mono text-muted-foreground">
                              {formatShortcut(item.shortcut, true)}
                            </span>
                          )}
                        </DropdownMenuItem>
                      );
                    })}
                  </DropdownMenuContent>
                </DropdownMenu>
              </TooltipProvider>
            )}

Comment on lines +255 to +261
'min-w-4 h-4 px-0.5 text-[9px] font-bold rounded-full',
'bg-primary text-primary-foreground shadow-sm',
'animate-in fade-in zoom-in duration-200'
)}
>
{item.count > 99 ? '99' : item.count}
</span>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The px-0.5 padding for the collapsed count badge is inconsistent with the px-1 used in sidebar-footer.tsx for a similar badge. For better visual consistency across the sidebar, consider unifying this padding to px-1.

                          <span
                            className={cn(
                              'absolute -top-1.5 -right-1.5 flex items-center justify-center',
                              'min-w-4 h-4 px-1 text-[9px] font-bold rounded-full',
                              'bg-primary text-primary-foreground shadow-sm',
                              'animate-in fade-in zoom-in duration-200'
                            )}
                          >
                            {item.count > 99 ? '99' : item.count}
                          </span>

Comment on lines +148 to 149
// Auto-collapse sidebar on small screens
useSidebarAutoCollapse({ sidebarOpen, toggleSidebar });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The comment for useSidebarAutoCollapse states "Auto-collapse sidebar on small screens", but the hook itself (in use-sidebar-auto-collapse.ts) still includes logic to "Update Electron window minWidth". The comment in sidebar.tsx should accurately reflect all functionalities of the hook for clarity.

Suggested change
// Auto-collapse sidebar on small screens
useSidebarAutoCollapse({ sidebarOpen, toggleSidebar });
// Auto-collapse sidebar on small screens and update Electron window minWidth
useSidebarAutoCollapse({ sidebarOpen, toggleSidebar });

stefandevo and others added 2 commits January 23, 2026 01:47
…ebar

- Add try/catch for getElectronAPI() in sidebar-footer with window.open fallback
- Use formatShortcut() for OS-aware hotkey display in sidebar-header
- Remove unnecessary optional chaining on project.icon
- Remove redundant ternary in sidebar-navigation className
- Update E2E tests to use new project-dropdown-trigger data-testid

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@stefandevo stefandevo merged commit 01859f3 into AutoMaker-Org:v0.14.0rc Jan 23, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Testers-Requested Request for others to test an enhancement or bug fix/etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant