Skip to content

refactor: update AppTitleBar and SidebarHeader components#6341

Merged
bijin-bruno merged 2 commits intousebruno:mainfrom
abhishek-bruno:refactor/workspace-refactor
Dec 8, 2025
Merged

refactor: update AppTitleBar and SidebarHeader components#6341
bijin-bruno merged 2 commits intousebruno:mainfrom
abhishek-bruno:refactor/workspace-refactor

Conversation

@abhishek-bruno
Copy link
Member

@abhishek-bruno abhishek-bruno commented Dec 8, 2025

Description

Update AppTitleBar and SidebarHeader components to use MenuDropdown and ActionIcon for improved UI consistency

  • Replaced Dropdown with MenuDropdown in AppTitleBar and SidebarHeader for workspace and collection actions.
  • Introduced ActionIcon for buttons in AppTitleBar and SidebarHeader to enhance accessibility and styling.
  • Cleaned up unused state and refactored workspace dropdown logic for better performance.
  • Updated styles in StyledWrapper to align with new component structure.

Contribution Checklist:

  • I've used AI significantly to create this pull request
  • The pull request only addresses one issue or adds one feature.
  • The pull request does not introduce any breaking changes
  • I have added screenshots or gifs to help explain the change if applicable.
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.

Publishing to New Package Managers

Please see here for more information.

Summary by CodeRabbit

  • New Features

    • Added a reusable icon button and a keyboard-navigable menu dropdown for consistent actions and menus.
  • Style

    • Simplified title bar layout, removed some action button visuals, updated branding color, and adjusted header padding and dropdown spacing/focus styles.
  • Refactor

    • Replaced ad-hoc dropdowns and native buttons with unified menu/action components across title bar and sidebar.
  • Tests

    • Updated test selectors to use test-id based locators for collection add/actions and related helpers.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 8, 2025

Walkthrough

Replaces ad-hoc buttons and Dropdowns with new reusable ActionIcon and MenuDropdown; refactors AppTitleBar and SidebarHeader to use them; updates dropdown focus/styling and titlebar CSS; moves SidebarHeader padding into its header element and updates many tests to use new test-id selectors.

Changes

Cohort / File(s) Summary
New UI components
packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js, packages/bruno-app/src/ui/ActionIcon/index.js, packages/bruno-app/src/ui/MenuDropdown/index.js
Adds ActionIcon (polymorphic accessible icon button with variants/sizes and optional hover color) and MenuDropdown (keyboard-navigable menu wrapper with items/labels/dividers, focus management, keyboard handling, and lifecycle hooks).
AppTitleBar refactor & styling
packages/bruno-app/src/components/AppTitleBar/index.js, packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js
Replaces native buttons with ActionIcon, swaps workspace dropdown for MenuDropdown, centralizes workspace menu items/handlers, removes old dropdown refs/state and explicit close logic, deletes several titlebar button/right-section styles and drag-region CSS, and updates branding text color.
SidebarHeader refactor & spacing
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js, packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js
Replaces imperative Dropdown usage with declarative MenuDropdown and ActionIcon, converts dropdowns to item arrays, removes ref-based control, and moves padding from wrapper to .sidebar-header.
Dropdown styling updates
packages/bruno-app/src/components/Dropdown/StyledWrapper.js
Adds focus-outline suppression and :focus-visible rules for [role="menu"], introduces .dropdown-label and .dropdown-right-section, and extends focus/selected styles while preserving hover/disabled behavior.
Tests & test helpers
tests/utils/page/locators.ts, tests/utils/page/actions.ts, ...tests/**
Updates many tests and helpers to use test-id collections-header-add-menu for the add menu and collections-header-actions-menu-close-all for close-all; replaces prior .plus-icon-button/old test ids across numerous spec files.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Trigger as ActionIcon (trigger)
    participant Menu as MenuDropdown
    participant Item as MenuItem
    participant App as AppHandler

    User->>Trigger: click / keyboard activate
    Trigger->>Menu: open()
    Menu->>Item: render items, focus selected/first

    alt keyboard navigation
        User->>Menu: Arrow/Home/End
        Menu->>Item: change focus
        User->>Menu: Enter/Space
        Item->>App: invoke item.onClick()
        App->>Menu: optionally close()
    else click item
        User->>Item: click
        Item->>App: invoke item.onClick()
        App->>Menu: optionally close()
    end

    User->>Menu: Escape / outside click
    Menu->>Menu: close()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Review MenuDropdown keyboard/focus logic (Home/End behavior, selectedItemId focus-on-open, focus restoration).
  • Verify ActionIcon polymorphic rendering, title/aria-label handling, and size/color mappings.
  • Confirm AppTitleBar and SidebarHeader event wiring after removal of imperative refs/explicit close calls.
  • Check StyledWrapper CSS changes in AppTitleBar and Dropdown for visual regressions and draggable-region implications.
  • Validate test locator changes in tests/utils/page/locators.ts and dependent specs for CI stability.

Possibly related PRs

Suggested reviewers

  • lohit-bruno
  • naman-bruno
  • bijin-bruno

Poem

Action icons now hum with tidy cheer,
Menus find focus when the keys draw near.
Padding slips gently to its rightful place,
Old refs step aside — a cleaner interface. ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 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 refactoring effort: replacing Dropdown with MenuDropdown and introducing ActionIcon components in AppTitleBar and SidebarHeader.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Copy link
Contributor

@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: 1

🧹 Nitpick comments (6)
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (1)

171-221: Simplify onClick handlers in dropdown item configs.

The arrow function wrappers are unnecessary when the handler takes no arguments.

   const addDropdownItems = [
     {
       id: 'create',
       leftSection: IconPlus,
       label: 'Create collection',
-      onClick: () => {
-        setCreateCollectionModalOpen(true);
-      }
+      onClick: () => setCreateCollectionModalOpen(true)
     },
     {
       id: 'import',
       leftSection: IconDownload,
       label: 'Import collection',
-      onClick: () => {
-        setImportCollectionModalOpen(true);
-      }
+      onClick: () => setImportCollectionModalOpen(true)
     },
     {
       id: 'open',
       leftSection: IconFolder,
       label: 'Open collection',
-      onClick: () => {
-        handleOpenCollection();
-      }
+      onClick: handleOpenCollection
     }
   ];

   const actionsDropdownItems = [
     {
       id: 'sort',
       leftSection: getSortIcon(),
       label: getSortLabel(),
-      onClick: () => {
-        handleSortCollections();
-      },
+      onClick: handleSortCollections,
       title: 'Sort collections',
       testId: 'sort-collections-button'
     },
     {
       id: 'close-all',
       leftSection: IconSquareX,
       label: 'Close all',
-      onClick: () => {
-        selectAllCollectionsToClose();
-      },
+      onClick: selectAllCollectionsToClose,
       title: 'Close all collections',
       testId: 'close-all-collections-button'
     }
   ];
packages/bruno-app/src/ui/MenuDropdown/index.js (2)

1-2: Consolidate React imports.

Minor nit: combine the React imports into a single statement.

-import React from 'react';
-import { useRef, useCallback, useState } from 'react';
+import React, { useRef, useCallback, useState } from 'react';

256-272: Unused itemIndex variable.

itemIndex is declared but only incremented for dividers. Since dividers can use the map index directly, this variable is unnecessary.

   const renderMenuContent = () => {
-    let itemIndex = 0;
-
-    return items.map((item) => {
+    return items.map((item, index) => {
       const itemType = item.type || 'item';

       if (itemType === 'label') {
         return renderLabel(item);
       }

       if (itemType === 'divider') {
-        return renderDivider(item, itemIndex++);
+        return renderDivider(item, index);
       }

       return renderMenuItem(item);
     });
   };
packages/bruno-app/src/components/AppTitleBar/index.js (1)

59-66: Move WorkspaceName outside the component or memoize it.

Defining forwardRef components inside render creates a new component type each render, causing React to unmount/remount it unnecessarily.

Move it outside AppTitleBar:

+const WorkspaceName = forwardRef(({ workspaceName, ...props }, ref) => {
+  return (
+    <div ref={ref} className="workspace-name-container" {...props}>
+      <span className="workspace-name">{workspaceName || 'Default Workspace'}</span>
+      <IconChevronDown size={14} stroke={1.5} className="chevron-icon" />
+    </div>
+  );
+});

 const AppTitleBar = () => {
   // ...
-  const WorkspaceName = forwardRef((props, ref) => {
-    return (
-      <div ref={ref} className="workspace-name-container" {...props}>
-        <span className="workspace-name">{toTitleCase(activeWorkspace?.name) || 'Default Workspace'}</span>
-        <IconChevronDown size={14} stroke={1.5} className="chevron-icon" />
-      </div>
-    );
-  });

Then update usage:

<WorkspaceName workspaceName={toTitleCase(activeWorkspace?.name)} />
packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js (1)

244-264: Consider the specificity implications of !important.

The !important declarations in colorOnHover ensure the hover color always applies (useful for destructive actions), but they make the styles harder to override in edge cases. If this is the intended behavior, this is acceptable.

packages/bruno-app/src/ui/ActionIcon/index.js (1)

4-19: Minor JSDoc clarification.

Line 16 documents ariaLabel but the actual destructured prop is 'aria-label' (line 29). For clarity, consider documenting it as @param {string} props['aria-label'] or adding a note that it maps to the standard aria-label attribute.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc107f8 and 51418ae.

📒 Files selected for processing (7)
  • packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/AppTitleBar/index.js (6 hunks)
  • packages/bruno-app/src/components/Dropdown/StyledWrapper.js (3 hunks)
  • packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (3 hunks)
  • packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/ui/ActionIcon/index.js (1 hunks)
  • packages/bruno-app/src/ui/MenuDropdown/index.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-app/src/components/AppTitleBar/index.js
  • packages/bruno-app/src/ui/MenuDropdown/index.js
  • packages/bruno-app/src/ui/ActionIcon/index.js
  • packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js
  • packages/bruno-app/src/components/Dropdown/StyledWrapper.js
  • packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js
  • packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js
🧠 Learnings (3)
📚 Learning: 2025-12-05T20:31:32.975Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:32.975Z
Learning: Applies to **/*.{jsx,tsx} : Styled Components are used as wrappers to define both self and children components style; Tailwind classes are used specifically for layout based styles

Applied to files:

  • packages/bruno-app/src/ui/ActionIcon/index.js
  • packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js
  • packages/bruno-app/src/components/Dropdown/StyledWrapper.js
  • packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js
📚 Learning: 2025-12-05T20:31:32.975Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:32.975Z
Learning: Applies to **/*.{jsx,tsx} : Use styled component's theme prop to manage CSS colors and not CSS variables when in the context of a styled component or any React component using the styled component

Applied to files:

  • packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js
  • packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js
📚 Learning: 2025-12-05T20:31:32.975Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:32.975Z
Learning: Applies to **/*.{jsx,tsx} : Styled Component CSS might also change layout but Tailwind classes shouldn't define colors

Applied to files:

  • packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js
  • packages/bruno-app/src/components/Dropdown/StyledWrapper.js
  • packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js
🧬 Code graph analysis (4)
packages/bruno-app/src/components/AppTitleBar/index.js (8)
packages/bruno-app/src/components/Sidebar/SidebarHeader/WorkspaceSelector/index.js (2)
  • sortedWorkspaces (21-23)
  • preferences (18-18)
packages/bruno-app/src/components/Sidebar/SidebarHeader/CloseWorkspace/index.js (1)
  • workspace (11-11)
packages/bruno-app/src/utils/workspaces/index.js (1)
  • isPinned (51-51)
packages/bruno-app/src/utils/common/index.js (2)
  • toTitleCase (353-360)
  • toTitleCase (353-360)
packages/bruno-app/src/ui/ActionIcon/index.js (1)
  • ActionIcon (20-54)
packages/bruno-app/src/ui/MenuDropdown/index.js (1)
  • MenuDropdown (31-295)
packages/bruno-app/src/components/Sidebar/index.js (1)
  • sidebarCollapsed (14-14)
packages/bruno-app/src/pages/Bruno/index.js (1)
  • isConsoleOpen (57-57)
packages/bruno-app/src/ui/MenuDropdown/index.js (2)
packages/bruno-app/src/ui/ActionIcon/index.js (1)
  • resolvedLabel (34-34)
packages/bruno-app/src/components/Dropdown/index.js (1)
  • Dropdown (5-25)
packages/bruno-app/src/ui/ActionIcon/index.js (1)
packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js (1)
  • StyledWrapper (11-265)
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (4)
packages/bruno-app/src/components/WorkspaceHome/index.js (1)
  • handleOpenCollection (65-71)
packages/bruno-app/src/ui/ActionIcon/index.js (1)
  • ActionIcon (20-54)
packages/bruno-app/src/ui/MenuDropdown/index.js (1)
  • MenuDropdown (31-295)
packages/bruno-app/src/components/Sidebar/index.js (1)
  • activeView (18-18)
🔇 Additional comments (21)
packages/bruno-app/src/components/Dropdown/StyledWrapper.js (3)

28-36: LGTM! Focus outline suppression for custom keyboard navigation.

Correctly removes native outlines on [role="menu"] elements, delegating visual focus indication to the .selected-focused class managed by MenuDropdown's keyboard navigation.


72-74: LGTM! Flex layout additions for dropdown item structure.

.dropdown-label with flex: 1 allows labels to fill available space, while .dropdown-right-section positions trailing elements (e.g., pin buttons, check icons) consistently. Aligns with MenuDropdown's renderMenuItem output.

Also applies to: 87-93


99-110: LGTM! Focus state styling for keyboard navigation.

The .selected-focused, :focus-visible, and :focus:not(:focus-visible) rules provide proper visual feedback while suppressing unwanted outlines—matches the focus management in MenuDropdown.

packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js (1)

92-97: LGTM! Improved branding text visibility.

Changing from theme.sidebar.muted to theme.text makes the "Bruno" branding more prominent. Style delegation to ActionIcon handles the removed button styles.

packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (2)

226-258: LGTM! Clean migration to ActionIcon and MenuDropdown.

The refactored action buttons are well-structured with proper labels, consistent icon sizing, and aria-hidden="true" on decorative icons.


280-311: Excellent accessibility improvements.

Good addition of role="tablist", role="tab", aria-selected, aria-controls, and aria-hidden attributes. This significantly improves screen reader support.

packages/bruno-app/src/ui/MenuDropdown/index.js (2)

103-130: LGTM! Solid keyboard navigation implementation.

Proper handling of ArrowDown/Up with wrapping, Home/End shortcuts, and Escape to close. Focus management with scrollIntoView ensures visibility.


142-176: LGTM! Focus initialization on dropdown open.

The onShow hook correctly focuses the selectedItemId item when provided, falling back to the menu container. The setTimeout(..., 0) ensures the DOM is ready.

packages/bruno-app/src/components/AppTitleBar/index.js (3)

123-174: LGTM! Well-structured workspace menu items.

The useMemo correctly memoizes the menu items with appropriate dependencies. The item structure with rightSection containing ActionIcon for pin actions is clean and accessible.


185-201: LGTM! ActionIcon and MenuDropdown integration.

Home button and workspace dropdown are properly wired. Using selectedItemId={activeWorkspaceUid} ensures the active workspace is focused on open—good UX.


213-244: LGTM! Consistent ActionIcon usage for toolbar actions.

All toggle buttons use ActionIcon with appropriate dynamic labels based on state (sidebarCollapsed, isConsoleOpen, orientation). Size is consistently "lg" for the titlebar actions.

packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js (6)

1-9: LGTM!

Clean import structure and well-defined size presets.


11-32: Base button styles look solid.

The size computation is flexible, handling both preset strings and numeric values. The pattern falls back gracefully for numeric sizes.


34-76: Subtle variant implementation is correct.

The multi-level theme token fallbacks (e.g., sidebar.muted → text.muted → #888) ensure the component degrades gracefully across different theme configurations.


78-219: All variant implementations are well-structured.

The filled, outline, light, and transparent variants follow a consistent pattern with appropriate theme token usage and state handling.


228-231: Verify keyboard accessibility with custom focus styles.

Removing the default focus outline relies on the background color change in each variant as the focus indicator. Ensure this provides sufficient contrast and visibility for keyboard navigation users, especially in the transparent variant where hover/focus only changes text color.


233-242: LGTM!

Smooth transitions and flex-shrink prevention ensure a polished user experience and maintain icon integrity.

packages/bruno-app/src/ui/ActionIcon/index.js (4)

1-3: LGTM!

Clean import structure.


20-32: LGTM!

Props destructuring with sensible defaults and proper polymorphic component support.


33-37: LGTM!

Label resolution priority and className composition are both handled correctly.


39-53: LGTM!

Correct use of styled-components polymorphism with the as prop, proper $ prefixing for transient props, and good accessibility attributes by setting both title and aria-label from the resolved label.

Copy link
Contributor

@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: 1

🧹 Nitpick comments (2)
packages/bruno-app/src/ui/MenuDropdown/index.js (2)

145-167: Consider alternatives to setTimeout(0) for focus management.

The setTimeout(0) pattern works but is fragile and depends on the browser's event loop timing. While this is a common pattern for deferring operations until the DOM is ready, it can occasionally fail if rendering takes longer than expected.

Consider using requestAnimationFrame for more reliable timing, or checking element readiness before focusing:

-        onShow: () => {
-          // Focus selected item if available, otherwise focus menu container
-          setTimeout(() => {
-            const menuContainer = ref.popper?.querySelector('[role="menu"]');
-            if (!menuContainer) return;
-
-            // If selectedItemId is provided, find and focus that item
-            if (selectedItemId) {
+        onShow: () => {
+          // Focus selected item if available, otherwise focus menu container
+          requestAnimationFrame(() => {
+            const menuContainer = ref.popper?.querySelector('[role="menu"]');
+            if (!menuContainer) return;
+
+            // If selectedItemId is provided, find and focus that item
+            if (selectedItemId) {
               const menuItems = Array.from(
                 menuContainer.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])')
               );
-
+
               const selectedItem = menuItems.find(
                 (item) => item.getAttribute('data-item-id') === selectedItemId
               );
-
+
               if (selectedItem) {
                 focusMenuItem(selectedItem, true);
                 return;
               }
             }
-
+
             // Fallback: focus menu container
             menuContainer.focus();
-          }, 0);
+          });
         }

206-216: Document rightSection click isolation behavior.

The click handler on the rightSection wrapper prevents event propagation to the parent menuitem, which allows interactive elements in the rightSection without triggering the menu item action. This is likely intentional but isn't documented in the JSDoc or inline comments.

Add an inline comment explaining this behavior:

         {item.rightSection && (
           <div
             className="dropdown-right-section"
+            // Prevent clicks on rightSection from triggering the menu item action
             onClick={(e) => {
               e.preventDefault();
               e.stopPropagation();
             }}
           >

Additionally, update the JSDoc to clarify that rightSection elements can be interactive without triggering the menu item click.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 51418ae and c6d4aef.

📒 Files selected for processing (1)
  • packages/bruno-app/src/ui/MenuDropdown/index.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-app/src/ui/MenuDropdown/index.js
🔇 Additional comments (1)
packages/bruno-app/src/ui/MenuDropdown/index.js (1)

252-260: Past review concern properly addressed.

The cloneElement call now correctly composes the child's existing onClick handler with handleTriggerClick, preserving both behaviors. This addresses the concern raised in the previous review about overwriting child onClick handlers.


// Render menu item
const renderMenuItem = (item) => {
const resolvedLabel = item.label || item.title || item.ariaLabel;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Incorrect fallback order for accessibility.

The resolvedLabel fallback order prioritizes label first, but ariaLabel should have highest priority since it's specifically intended to override visible labels when needed for accessibility.

Apply this diff to correct the fallback order:

-    const resolvedLabel = item.label || item.title || item.ariaLabel;
+    const resolvedLabel = item.ariaLabel || item.label || item.title;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const resolvedLabel = item.label || item.title || item.ariaLabel;
const resolvedLabel = item.ariaLabel || item.label || item.title;
🤖 Prompt for AI Agents
In packages/bruno-app/src/ui/MenuDropdown/index.js around line 189, the
resolvedLabel currently falls back in the wrong order (label -> title ->
ariaLabel); change the fallback so ariaLabel has highest priority by resolving
as ariaLabel first, then label, then title (i.e., use item.ariaLabel ||
item.label || item.title) so accessibility overrides are respected.

Copy link
Contributor

@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: 0

♻️ Duplicate comments (1)
packages/bruno-app/src/ui/MenuDropdown/index.js (1)

187-217: Add aria-label fallback for accessibility.

The aria-label only uses item.ariaLabel directly. If a consumer provides only label or title, the accessibility label will be undefined. This was flagged in a prior review.

Apply this diff to add proper fallback:

         aria-label={item.ariaLabel}
+        aria-label={item.ariaLabel || item.label || item.title}

Also, if ariaLabel is undefined and no fallback is provided, consider omitting the attribute entirely to avoid rendering aria-label="":

-        aria-label={item.ariaLabel}
+        aria-label={item.ariaLabel || item.label || item.title || undefined}
🧹 Nitpick comments (3)
packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js (1)

22-51: Consider validating the $size prop fallback.

Lines 32-33 fall back to props.$size directly if it's not in sizeMap. This allows custom numeric sizes but could produce invalid CSS (e.g., undefinedpx or invalidpx) if an unexpected value is passed.

Consider adding a safer fallback:

-  width: ${(props) => sizeMap[props.$size] || props.$size}px;
-  height: ${(props) => sizeMap[props.$size] || props.$size}px;
+  width: ${(props) => sizeMap[props.$size] || props.$size || sizeMap.md}px;
+  height: ${(props) => sizeMap[props.$size] || props.$size || sizeMap.md}px;
packages/bruno-app/src/ui/MenuDropdown/index.js (2)

1-2: Consolidate React imports.

Minor style nit: these can be combined into a single import statement.

-import React from 'react';
-import { useRef, useCallback, useState } from 'react';
+import React, { useRef, useCallback, useState } from 'react';

71-88: Wrap focusMenuItem in useCallback.

This function is referenced inside handleMenuKeyDown and onDropdownCreate but isn't memoized. This could cause unnecessary re-renders or stale closure issues. ESLint's exhaustive-deps rule would likely flag this.

-  // Focus a menu item
-  const focusMenuItem = (item, addSelectedClass = false) => {
+  // Focus a menu item
+  const focusMenuItem = useCallback((item, addSelectedClass = false) => {
     if (item) {
       // Remove selected class from all items first
       const menuContainer = item.closest('[role="menu"]');
       if (menuContainer) {
         menuContainer.querySelectorAll('.selected-focused').forEach((el) => {
           el.classList.remove('selected-focused');
         });
       }

       if (addSelectedClass) {
         item.classList.add('selected-focused');
       }
       item.focus();
       item.scrollIntoView({ block: 'nearest' });
     }
-  };
+  }, []);

Then add focusMenuItem to the dependency arrays of handleMenuKeyDown (line 126) and onDropdownCreate (line 171).

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c6d4aef and cd2299f.

📒 Files selected for processing (3)
  • packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/ui/ActionIcon/index.js (1 hunks)
  • packages/bruno-app/src/ui/MenuDropdown/index.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/bruno-app/src/ui/ActionIcon/index.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-app/src/ui/MenuDropdown/index.js
  • packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js
🧠 Learnings (3)
📚 Learning: 2025-12-05T20:31:32.975Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:32.975Z
Learning: Applies to **/*.{jsx,tsx} : Styled Components are used as wrappers to define both self and children components style; Tailwind classes are used specifically for layout based styles

Applied to files:

  • packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js
📚 Learning: 2025-12-05T20:31:32.975Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:32.975Z
Learning: Applies to **/*.{jsx,tsx} : Styled Component CSS might also change layout but Tailwind classes shouldn't define colors

Applied to files:

  • packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js
📚 Learning: 2025-12-05T20:31:32.975Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:32.975Z
Learning: Applies to **/*.{jsx,tsx} : Use styled component's theme prop to manage CSS colors and not CSS variables when in the context of a styled component or any React component using the styled component

Applied to files:

  • packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js
🧬 Code graph analysis (1)
packages/bruno-app/src/ui/MenuDropdown/index.js (1)
packages/bruno-app/src/components/Dropdown/index.js (1)
  • Dropdown (5-25)
🔇 Additional comments (9)
packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js (2)

1-9: LGTM!

Clean import and size mapping. The predefined size constants provide good consistency across the app.


11-20: LGTM!

Variant styling correctly uses theme props for colors. Based on learnings, this follows the pattern of using styled component's theme prop for CSS colors rather than CSS variables or Tailwind classes.

packages/bruno-app/src/ui/MenuDropdown/index.js (7)

5-16: LGTM!

Clean extraction of constants and the navigation index calculation helper. The wrap-around logic for ArrowUp/ArrowDown is correct.


18-39: Good JSDoc documentation.

Comprehensive documentation of the item structure and props. This will help consumers understand the API.


90-126: LGTM!

Keyboard navigation handler is well-structured with proper event prevention and clean separation of Escape, action keys, and navigation keys.


138-171: LGTM on Tippy setup.

The setTimeout(..., 0) is a reasonable approach to defer focus until after the dropdown is rendered.


219-248: LGTM!

Label and divider rendering is clean with proper ARIA roles. Key generation handles both explicit IDs and fallback index-based keys.


250-258: LGTM!

Good fix on composing the child's onClick handler before calling handleTriggerClick. This preserves any existing click behavior on the trigger element.


260-275: LGTM!

Clean integration with the underlying Dropdown component. Controlled visibility via isOpen state and proper outside-click handling.

Copy link
Contributor

@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: 0

🧹 Nitpick comments (3)
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (3)

171-219: Dropdown item configuration is clear and correctly wired

The addDropdownItems and actionsDropdownItems arrays are well-structured: ids, icons, labels, and handlers are all consistent, and the sort label/icon correctly represent the next sort state, not the current one. If you ever see unnecessary re-renders in MenuDropdown, these arrays would be good candidates for useMemo, but that’s an optional micro-optimization for now.


224-257: ActionIcon + MenuDropdown wiring for collections actions is solid

Search, add, and “more actions” are all correctly routed through existing handlers, and the ActionIcon labels plus aria-hidden icons give you good accessibility semantics. If you later surface a “search is open” boolean from the parent, it may be worth threading that into ActionIcon (e.g., as an active/aria-pressed-like state), but that’s purely a UX refinement.


272-305: ARIA updates for the view switcher improve semantics; consider full tabs pattern

Using role="heading" for the single view and tablist/tab/aria-selected on the multi-view buttons is a nice accessibility upgrade, and icons being aria-hidden avoids duplicate announcements. If the associated panels aren’t already wired elsewhere, you may want to pair each tab with a corresponding role="tabpanel" element whose id matches aria-controls to fully align with the WAI-ARIA tabs pattern.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd2299f and 6bd16c3.

📒 Files selected for processing (1)
  • packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js
🔇 Additional comments (1)
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (1)

15-25: Imports for useState, MenuDropdown, and ActionIcon look correct

The new imports are clean and correctly scoped to support the refactor to shared UI components.

@abhishek-bruno abhishek-bruno force-pushed the refactor/workspace-refactor branch from 6bd16c3 to 20dbdd5 Compare December 8, 2025 13:57
Copy link
Contributor

@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: 4

♻️ Duplicate comments (1)
packages/bruno-app/src/ui/MenuDropdown/index.js (1)

18-39: Align item label / aria-label / title behavior with JSDoc and A11y expectations

The JSDoc says label is used for both title and aria-label (preferred) and that ariaLabel/title fall back accordingly, but renderMenuItem currently wires these independently:

  • Visible text: item.label
  • aria-label: item.ariaLabel
  • title: item.title

So items with only label don’t get an explicit aria-label/title, and items with only title don’t expose that to A11y unless the text content matches.

To match the docs and prioritize A11y overrides, consider resolving once and reusing:

-  const renderMenuItem = (item) => {
-    return (
+  const renderMenuItem = (item) => {
+    const resolvedLabel = item.ariaLabel || item.label || item.title;
+    const resolvedTitle = item.title || resolvedLabel;
+
+    return (
       <div
         key={item.id}
         className={`dropdown-item ${item.disabled ? 'disabled' : ''} ${item.className || ''}`.trim()}
         role="menuitem"
         data-item-id={item.id}
         onClick={() => !item.disabled && handleItemClick(item)}
         tabIndex={item.disabled ? -1 : 0}
-        aria-label={item.ariaLabel}
+        aria-label={resolvedLabel}
         aria-disabled={item.disabled}
-        title={item.title}
+        title={resolvedTitle}
         data-testid={item.testId}
       >
         {renderSection(item.leftSection)}
-        <span className="dropdown-label">{item.label}</span>
+        <span className="dropdown-label">{item.label || resolvedLabel}</span>
         ...
       </div>
-    );
+    );
   };

This gives ariaLabel highest priority, falls back to label/title, and keeps the docs accurate.

Also applies to: 187-201

🧹 Nitpick comments (2)
packages/bruno-app/src/ui/ActionIcon/index.js (1)

35-48: Default ActionIcon’s native button to type="button"

When component is left as the default 'button', the underlying element will be a submit button inside forms, which can cause unexpected submissions.

You can guard against this by defaulting the type when rendering a native button:

 const ActionIcon = ({
   ...
   component: Component = 'button',
   ...
   colorOnHover,
-  ...rest
+  ...rest
 }) => {
   ...
-  return (
-    <StyledWrapper
-      as={Component}
+  const { type, ...restProps } = rest;
+
+  return (
+    <StyledWrapper
+      as={Component}
       $variant={variant}
       $size={size}
       $colorOnHover={colorOnHover}
       disabled={disabled}
       className={classNames}
-      title={label}
-      aria-label={ariaLabel}
-      {...rest}
+      {...(Component === 'button' ? { type: type || 'button' } : {})}
+      {...restProps}
     >
       {children}
     </StyledWrapper>
   );
 };

This keeps existing behavior for non-button components while making the default safe inside forms.

packages/bruno-app/src/components/AppTitleBar/index.js (1)

59-67: Tidy WorkspaceName definition and workspaceMenuItems dependencies

Functionally this works, but a couple of small refactors would make it cleaner and more lint-friendly:

  • WorkspaceName is created with forwardRef inside AppTitleBar, so a new component type is allocated every render. Moving it outside the component (or wrapping the forwardRef call in a useMemo([])) would stabilize its identity.
  • workspaceMenuItems’s useMemo depends on sortedWorkspaces, activeWorkspaceUid, preferences, and handlePinWorkspace, but it also closes over handleWorkspaceSwitch, handleCreateWorkspace, and handleOpenWorkspace. They’re stable enough in practice, but strict hook linting would either ask to add them as dependencies (or wrap them in useCallback) or to drop useMemo if the optimization isn’t needed.

None of this is blocking, but worth tightening up to keep hook rules and component identity straightforward.

Also applies to: 123-175

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6bd16c3 and a754dab.

📒 Files selected for processing (8)
  • packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/AppTitleBar/index.js (6 hunks)
  • packages/bruno-app/src/components/Dropdown/StyledWrapper.js (3 hunks)
  • packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (3 hunks)
  • packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/ui/ActionIcon/index.js (1 hunks)
  • packages/bruno-app/src/ui/MenuDropdown/index.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js
  • packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js
  • packages/bruno-app/src/components/Dropdown/StyledWrapper.js
  • packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js
  • packages/bruno-app/src/components/AppTitleBar/index.js
  • packages/bruno-app/src/ui/MenuDropdown/index.js
  • packages/bruno-app/src/ui/ActionIcon/index.js
🧠 Learnings (3)
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{jsx,tsx} : Styled Components are used as wrappers to define both self and children components style; Tailwind classes are used specifically for layout based styles

Applied to files:

  • packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js
  • packages/bruno-app/src/components/Dropdown/StyledWrapper.js
  • packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js
  • packages/bruno-app/src/ui/ActionIcon/index.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{jsx,tsx} : Styled Component CSS might also change layout but Tailwind classes shouldn't define colors

Applied to files:

  • packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js
  • packages/bruno-app/src/components/Dropdown/StyledWrapper.js
  • packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{jsx,tsx} : Use styled component's theme prop to manage CSS colors and not CSS variables when in the context of a styled component or any React component using the styled component

Applied to files:

  • packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js
🧬 Code graph analysis (3)
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (5)
packages/bruno-app/src/components/WorkspaceHome/index.js (1)
  • handleOpenCollection (65-71)
packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecItem/index.js (1)
  • handleOpenApiSpec (21-24)
packages/bruno-app/src/components/Sidebar/ApiSpecs/index.js (1)
  • handleOpenApiSpec (35-39)
packages/bruno-app/src/ui/ActionIcon/index.js (1)
  • ActionIcon (20-50)
packages/bruno-app/src/ui/MenuDropdown/index.js (1)
  • MenuDropdown (40-275)
packages/bruno-app/src/ui/MenuDropdown/index.js (1)
packages/bruno-app/src/components/Dropdown/index.js (1)
  • Dropdown (5-25)
packages/bruno-app/src/ui/ActionIcon/index.js (1)
packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js (1)
  • StyledWrapper (22-51)
🪛 GitHub Check: Unit Tests
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js

[failure] 209-209:
Unexpected trailing comma

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: CLI Tests
🔇 Additional comments (2)
packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js (1)

4-13: Padding moved from wrapper to .sidebar-header — layout change looks intentional, but validate usages

Scoping the padding: 8px 4px 6px 10px; to .sidebar-header instead of the root StyledWrapper should help keep spacing specific to the header row and aligns with the new ActionIcon/MenuDropdown structure. Just confirm there are no consumers that relied on wrapper‑level padding for any non‑header children (e.g., additional sections rendered inside this wrapper) so we don’t get content suddenly flush against the sidebar edge.

packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js (1)

92-97: Bruno branding color change looks good

Switching .bruno-text to theme.text aligns with styled-components theming guidance and should improve contrast against the sidebar background.

Comment on lines +28 to +36
[role="menu"] {
outline: none;
&:focus {
outline: none;
}
&:focus-visible {
outline: none;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Disabled styling doesn’t apply to aria-disabled menu items

MenuDropdown renders items as <div role="menuitem" aria-disabled="true">, so the &:disabled selector here never matches and disabled items won’t get the not-allowed cursor / reduced opacity. Also, outlines are fully suppressed on the menu container and focused items, so the hover background becomes the only focus affordance.

Consider extending selectors so both semantics and visuals line up, for example:

-      .dropdown-item {
+      .dropdown-item {
         ...
-        &:hover:not(:disabled) {
+        &:hover:not(:disabled):not([aria-disabled="true"]) {
           background-color: ${(props) => props.theme.dropdown.hoverBg};
         }
         ...
-        &:disabled {
-          cursor: not-allowed;
-          opacity: 0.5;
-        }
+        &:disabled,
+        &[aria-disabled="true"],
+        &.disabled {
+          cursor: not-allowed;
+          opacity: 0.5;
+        }
       }

This keeps keyboard focus visible via background while correctly styling disabled items coming from MenuDropdown.

Also applies to: 95-115

🤖 Prompt for AI Agents
In packages/bruno-app/src/components/Dropdown/StyledWrapper.js around lines
28-36 (and similarly update lines 95-115), the stylesheet uses &:disabled which
won't match elements using aria-disabled, and outlines are completely suppressed
causing no visible keyboard focus; update the selectors to target both the
disabled pseudo-class and the aria attribute (e.g., &[aria-disabled="true"],
&:disabled) and apply the disabled styles (cursor: not-allowed, reduced opacity)
to those selectors, and adjust focus rules to keep a visible focus affordance
(remove blanket outline: none on the container and use :focus-visible to show a
subtle background or box-shadow while still preventing outline suppression from
hiding keyboard focus).

Comment on lines +4 to +19
/**
* ActionIcon - A reusable icon button component
*
* @param {Object} props
* @param {ReactNode} props.children - The icon component to render
* @param {string} props.variant - Visual variant: 'subtle' (default), 'filled', 'outline', etc.
* @param {string} props.size - Size of the button: 'sm', 'md', 'lg', etc. (default: 'md')
* @param {boolean} props.disabled - Whether the button is disabled
* @param {string} props.className - Additional CSS class names
* @param {string} props.component - Polymorphic component (default: 'button')
* @param {string} props.label - Label for both title and aria-label (preferred)
* @param {string} props.title - Title attribute (falls back to label or aria-label)
* @param {string} [props.ariaLabel] - Accessibility label (falls back to label or title)
* @param {string} props.colorOnHover - Color to apply to icon on hover/focus (e.g., 'red', '#ef4444', 'var(--color-danger)')
* @param {Object} props...rest - Other props passed to the underlying element
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix ActionIcon label / aria-label resolution for accessible names

Right now label only drives title, and aria-label must be passed explicitly:

title={label}
aria-label={ariaLabel}

Most usages (e.g., Home, search, “More actions”) only pass label, so buttons end up relying on title and icon semantics rather than a clear aria-label, which is weaker for assistive tech.

To align with the JSDoc (“label for both title and aria-label (preferred)”) and ensure A11y:

 const ActionIcon = ({
   children,
   variant = 'subtle',
   size = 'md',
   disabled = false,
   className = '',
   component: Component = 'button',
   label,
   'aria-label': ariaLabel,
   colorOnHover,
   ...rest
 }) => {
   // Build className array and filter out empty strings
   const classNames = ['action-icon', className].filter(Boolean).join(' ');
+
+  const resolvedAriaLabel = ariaLabel || label || rest.title;
+  const resolvedTitle = rest.title || label || ariaLabel;

   return (
     <StyledWrapper
       as={Component}
       $variant={variant}
       $size={size}
       $colorOnHover={colorOnHover}
       disabled={disabled}
       className={classNames}
-      title={label}
-      aria-label={ariaLabel}
+      title={resolvedTitle}
+      aria-label={resolvedAriaLabel}
       {...rest}
     >
       {children}
     </StyledWrapper>
   );
 };

This gives aria-label explicit highest priority, with sensible fallbacks when only label or title is provided.

Also applies to: 20-45

🤖 Prompt for AI Agents
In packages/bruno-app/src/ui/ActionIcon/index.js around lines 4 to 19 (and
similarly update the block at lines 20 to 45), the component currently sets
title from label but leaves aria-label tied only to the explicit ariaLabel prop;
change the resolution so aria-label picks ariaLabel first, then label, then
title (ariaLabel ?? label ?? title) and set title to title ?? label ?? ariaLabel
to keep consistent fallbacks; update the JSX props to pass title and aria-label
using those computed fallbacks so components that only pass label get a proper
aria-label for accessibility.

…Dropdown and ActionIcon for improved UI consistency
@abhishek-bruno abhishek-bruno force-pushed the refactor/workspace-refactor branch from 92a36cc to b6c1d2c Compare December 8, 2025 14:32
Copy link
Contributor

@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: 1

♻️ Duplicate comments (1)
packages/bruno-app/src/ui/MenuDropdown/index.js (1)

228-230: Divider class name may still not match StyledWrapper’s styles

The divider is rendered with className="dropdown-divider". Previous styling in components/Dropdown/StyledWrapper.js targeted .dropdown-separator, and an earlier review already flagged this mismatch.

Unless the styles were updated to also cover .dropdown-divider, this separator still won’t pick up the intended visuals. Either align the class name here:

-  const renderDivider = (item, index) => (
-    <div key={item.id || `divider-${index}`} className="dropdown-divider" role="separator" />
-  );
+  const renderDivider = (item, index) => (
+    <div key={item.id || `divider-${index}`} className="dropdown-separator" role="separator" />
+  );

or alias both class names in the StyledWrapper so they share the same styling.

🧹 Nitpick comments (4)
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (1)

181-249: Dropdown item configs look good; consider memoizing for stability

addDropdownItems and actionsDropdownItems are rebuilt on every render, even though they only depend on stable handlers and Redux state. This is fine for now, but if the header ever re-renders frequently it may cause extra work for MenuDropdown reconciliation.

You could wrap these arrays in useMemo keyed on the minimal state they depend on (e.g. collectionSortOrder) to keep the item identities stable and reduce render churn, especially if tests rely on referential equality.

packages/bruno-app/src/components/AppTitleBar/index.js (2)

59-63: Preserve default workspace-name-container class when spreading props

Spreading props after className="workspace-name-container" means any incoming className will override the default, potentially dropping required styling if the component is reused elsewhere.

To make this more robust, consider explicitly merging classes instead of overwriting:

-  const WorkspaceName = forwardRef((props, ref) => {
-    return (
-      <div ref={ref} className="workspace-name-container" {...props}>
+  const WorkspaceName = forwardRef(({ className = '', ...rest }, ref) => {
+    return (
+      <div
+        ref={ref}
+        className={`workspace-name-container ${className}`.trim()}
+        {...rest}
+      >
         <span className="workspace-name">{toTitleCase(activeWorkspace?.name) || 'Default Workspace'}</span>
         <IconChevronDown size={14} stroke={1.5} className="chevron-icon" />
       </div>
     );
   });

123-174: Ensure workspaceMenuItems useMemo stays aligned with hooks lint and handlers

The workspaceMenuItems construction is solid (active checks, pin/unpin right section, workspace actions appended), but the useMemo dependency list omits the handler functions it closes over (handleWorkspaceSwitch, handleCreateWorkspace, handleOpenWorkspace).

To avoid future surprises and satisfy exhaustive-deps lint, either:

  • Wrap those handlers in useCallback and include them in the dependency array, or
  • Drop useMemo and build the menu inline if the workspace list is small enough that memoization isn’t buying much.

For example (one option):

-  const workspaceMenuItems = useMemo(() => {
+  const workspaceMenuItems = useMemo(() => {
     const items = sortedWorkspaces.map((workspace) => {
       const isActive = workspace.uid === activeWorkspaceUid;
       const isPinned = preferences?.workspaces?.pinnedWorkspaceUids?.includes(workspace.uid);
@@
     items.push(
       { type: 'label', label: 'Workspaces' },
       {
         id: 'create-workspace',
         leftSection: IconPlus,
         label: 'Create workspace',
         onClick: handleCreateWorkspace
       },
       {
         id: 'open-workspace',
         leftSection: IconFolder,
         label: 'Open workspace',
         onClick: handleOpenWorkspace
       }
     );
 
     return items;
-  }, [sortedWorkspaces, activeWorkspaceUid, preferences, handlePinWorkspace]);
+  }, [sortedWorkspaces, activeWorkspaceUid, preferences, handlePinWorkspace, handleWorkspaceSwitch, handleCreateWorkspace, handleOpenWorkspace]);
packages/bruno-app/src/ui/MenuDropdown/index.js (1)

6-16: Minor cleanup: remove Escape from NAVIGATION_KEYS to reflect actual behavior

Escape is included in NAVIGATION_KEYS, but it is handled separately and returns early in handleMenuKeyDown. As a result, it never reaches the navigation block, and having it in NAVIGATION_KEYS is slightly misleading.

You can simplify the intent by dropping it from the navigation list:

-const NAVIGATION_KEYS = ['ArrowDown', 'ArrowUp', 'Home', 'End', 'Escape'];
+const NAVIGATION_KEYS = ['ArrowDown', 'ArrowUp', 'Home', 'End'];

This keeps the key groups self-describing without changing behavior.

Also applies to: 91-127

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52621b1 and 92a36cc.

📒 Files selected for processing (4)
  • packages/bruno-app/src/components/AppTitleBar/index.js (6 hunks)
  • packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (3 hunks)
  • packages/bruno-app/src/ui/MenuDropdown/index.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js
  • packages/bruno-app/src/ui/MenuDropdown/index.js
  • packages/bruno-app/src/components/AppTitleBar/index.js
🧠 Learnings (2)
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : No trailing commas

Applied to files:

  • packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{jsx,tsx} : Styled Component CSS might also change layout but Tailwind classes shouldn't define colors

Applied to files:

  • packages/bruno-app/src/ui/MenuDropdown/index.js
🧬 Code graph analysis (2)
packages/bruno-app/src/ui/MenuDropdown/index.js (1)
packages/bruno-app/src/components/Dropdown/index.js (1)
  • Dropdown (5-25)
packages/bruno-app/src/components/AppTitleBar/index.js (8)
packages/bruno-app/src/components/Sidebar/SidebarHeader/WorkspaceSelector/index.js (2)
  • sortedWorkspaces (21-23)
  • preferences (18-18)
packages/bruno-app/src/components/Sidebar/SidebarHeader/CloseWorkspace/index.js (1)
  • workspace (11-11)
packages/bruno-app/src/utils/workspaces/index.js (1)
  • isPinned (51-51)
packages/bruno-app/src/utils/common/index.js (2)
  • toTitleCase (353-360)
  • toTitleCase (353-360)
packages/bruno-app/src/ui/ActionIcon/index.js (1)
  • ActionIcon (20-50)
packages/bruno-app/src/ui/MenuDropdown/index.js (1)
  • MenuDropdown (40-277)
packages/bruno-app/src/components/Sidebar/index.js (1)
  • sidebarCollapsed (14-14)
packages/bruno-app/src/pages/Bruno/index.js (1)
  • isConsoleOpen (61-61)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: Unit Tests
  • GitHub Check: CLI Tests
🔇 Additional comments (3)
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (1)

254-288: New ActionIcon + MenuDropdown wiring is clean and consistent

The search button and the two collection dropdowns are now nicely standardized on ActionIcon + MenuDropdown, with clear labels and test IDs. The callbacks (handleToggleSearch, addDropdownItems, actionsDropdownItems) are correctly threaded, and right now I don’t see correctness issues here.

packages/bruno-app/src/ui/MenuDropdown/index.js (1)

251-260: Trigger cloning preserves child onClick and adds test id—looks good

The updated cloneElement correctly composes the child’s existing onClick with handleTriggerClick, and also assigns a consistent data-testid to the trigger. This addresses the previous concern about overwriting consumer handlers without introducing breaking changes.

packages/bruno-app/src/components/AppTitleBar/index.js (1)

185-203: Icon-only ActionIcons rely on label for tooltips but may lack explicit aria-labels

The new ActionIcon usages (Home, toggle sidebar, devtools, layout) pass label but may not include explicit aria-label props. Verify the ActionIcon component implementation to confirm how it handles the label prop for accessibility purposes, and ensure explicit aria-label attributes are added to all icon-only button usages if the component doesn't automatically map label to aria-label.

Comment on lines +139 to +172
// Setup Tippy instance
const onDropdownCreate = useCallback((ref) => {
tippyRef.current = ref;
if (ref) {
ref.setProps({
onShow: () => {
// Focus selected item if available, otherwise focus menu container
setTimeout(() => {
const menuContainer = ref.popper?.querySelector('[role="menu"]');
if (!menuContainer) return;

// If selectedItemId is provided, find and focus that item
if (selectedItemId) {
const menuItems = Array.from(
menuContainer.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])')
);

const selectedItem = menuItems.find(
(item) => item.getAttribute('data-item-id') === selectedItemId
);

if (selectedItem) {
focusMenuItem(selectedItem, true);
return;
}
}

// Fallback: focus menu container
menuContainer.focus();
}, 0);
}
});
}
}, [selectedItemId]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n packages/bruno-app/src/ui/MenuDropdown/index.js | head -200

Repository: usebruno/bruno

Length of output: 172


Move focus behavior into a useEffect to keep selectedItemId in sync

The current code sets Tippy's onShow handler only during instance creation via onCreate. Since this callback fires once on mount, the onShow handler captures selectedItemId at that time and never updates when the value changes later (e.g., switching active workspace). This causes the menu to focus stale item IDs.

Move the setProps call into a useEffect that runs whenever selectedItemId changes:

-import { useRef, useCallback, useState } from 'react';
+import { useRef, useCallback, useState, useEffect } from 'react';
@@
-  // Setup Tippy instance
-  const onDropdownCreate = useCallback((ref) => {
-    tippyRef.current = ref;
-    if (ref) {
-      ref.setProps({
-        onShow: () => {
-          // Focus selected item if available, otherwise focus menu container
-          setTimeout(() => {
-            const menuContainer = ref.popper?.querySelector('[role="menu"]');
-            if (!menuContainer) return;
-
-            // If selectedItemId is provided, find and focus that item
-            if (selectedItemId) {
-              const menuItems = Array.from(
-                menuContainer.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])')
-              );
-
-              const selectedItem = menuItems.find(
-                (item) => item.getAttribute('data-item-id') === selectedItemId
-              );
-
-              if (selectedItem) {
-                focusMenuItem(selectedItem, true);
-                return;
-              }
-            }
-
-            // Fallback: focus menu container
-            menuContainer.focus();
-          }, 0);
-        }
-      });
-    }
-  }, [selectedItemId]);
+  // Setup Tippy instance
+  const onDropdownCreate = useCallback((ref) => {
+    tippyRef.current = ref;
+  }, []);
+
+  // Keep focus behavior in sync with selectedItemId
+  useEffect(() => {
+    const ref = tippyRef.current;
+    if (!ref) return;
+
+    ref.setProps({
+      onShow: () => {
+        // Focus selected item if available, otherwise focus menu container
+        setTimeout(() => {
+          const menuContainer = ref.popper?.querySelector('[role="menu"]');
+          if (!menuContainer) return;
+
+          if (selectedItemId) {
+            const menuItems = Array.from(
+              menuContainer.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])')
+            );
+
+            const selectedItem = menuItems.find(
+              (item) => item.getAttribute('data-item-id') === selectedItemId
+            );
+
+            if (selectedItem) {
+              focusMenuItem(selectedItem, true);
+              return;
+            }
+          }
+
+          // Fallback: focus menu container
+          menuContainer.focus();
+        }, 0);
+      }
+    });
+  }, [selectedItemId, focusMenuItem]);

Ensure focusMenuItem is memoized (e.g., with useCallback) to avoid unnecessary effect reruns.

🤖 Prompt for AI Agents
In packages/bruno-app/src/ui/MenuDropdown/index.js around lines 139 to 172, the
onShow handler is set only once during Tippy instance creation and therefore
captures a stale selectedItemId; move the ref.setProps({ onShow: ... }) call
into a useEffect that runs whenever selectedItemId (and focusMenuItem) changes
so the latest ID is used, and ensure focusMenuItem is memoized with useCallback
to avoid unnecessary effect reruns; attach the new onShow via
tippyRef.current?.setProps inside the effect and keep the existing
focus/fallback logic unchanged.

Copy link
Contributor

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/bruno-app/src/components/AppTitleBar/index.js (1)

59-66: Move WorkspaceName outside the component.

Defining forwardRef inside the render function creates a new component identity on every render, potentially causing unnecessary re-mounts. Move it outside AppTitleBar or use useMemo to stabilize it.

+const WorkspaceName = forwardRef(({ workspaceName, ...props }, ref) => {
+  return (
+    <div ref={ref} className="workspace-name-container" {...props}>
+      <span className="workspace-name">{workspaceName || 'Default Workspace'}</span>
+      <IconChevronDown size={14} stroke={1.5} className="chevron-icon" />
+    </div>
+  );
+});

 const AppTitleBar = () => {
   // ...
-  const WorkspaceName = forwardRef((props, ref) => {
-    return (
-      <div ref={ref} className="workspace-name-container" {...props}>
-        <span className="workspace-name">{toTitleCase(activeWorkspace?.name) || 'Default Workspace'}</span>
-        <IconChevronDown size={14} stroke={1.5} className="chevron-icon" />
-      </div>
-    );
-  });

Then use: <WorkspaceName workspaceName={toTitleCase(activeWorkspace?.name)} />

♻️ Duplicate comments (2)
packages/bruno-app/src/ui/MenuDropdown/index.js (2)

227-230: Separator class name may not match styles.

The divider uses className="dropdown-divider" but styled components may expect .dropdown-separator. Verify the styling is applied correctly or align the class name.

#!/bin/bash
# Check what separator class names are used in styled components
rg -n "dropdown-separator|dropdown-divider" --type=js

189-218: Add fallback for aria-label accessibility.

Line 198 only uses item.ariaLabel, but if it's undefined, the element won't have an accessible name. Add a fallback chain for better accessibility:

         tabIndex={item.disabled ? -1 : 0}
-        aria-label={item.ariaLabel}
+        aria-label={item.ariaLabel || item.label || item.title}
         aria-disabled={item.disabled}
🧹 Nitpick comments (3)
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (1)

181-227: Dropdown item configuration looks clean.

The declarative item structure for addDropdownItems is well-organized. Each item has a unique id, appropriate icon via leftSection, and onClick handlers.

One minor inconsistency: the onClick handlers use arrow functions wrapping single function calls (e.g., onClick: () => { handleOpenCollection(); }). These could be simplified.

     {
       id: 'open',
       leftSection: IconFolder,
       label: 'Open collection',
-      onClick: () => {
-        handleOpenCollection();
-      }
+      onClick: handleOpenCollection
     },

Apply similar simplification to other items where the handler is a direct function reference.

packages/bruno-app/src/components/AppTitleBar/index.js (1)

1-3: Consolidate React imports.

Lines 1 and 3 both import from 'react'. Consider consolidating:

-import React from 'react';
-import { IconCheck, IconChevronDown, IconFolder, IconHome, IconLayoutColumns, IconLayoutRows, IconPin, IconPinned, IconPlus } from '@tabler/icons';
-import { forwardRef, useCallback, useEffect, useMemo, useState } from 'react';
+import React, { forwardRef, useCallback, useEffect, useMemo, useState } from 'react';
+import { IconCheck, IconChevronDown, IconFolder, IconHome, IconLayoutColumns, IconLayoutRows, IconPin, IconPinned, IconPlus } from '@tabler/icons';
packages/bruno-app/src/ui/MenuDropdown/index.js (1)

1-2: Consolidate React imports.

Same pattern as AppTitleBar - consolidate the imports:

-import React from 'react';
-import { useRef, useCallback, useState } from 'react';
+import React, { useRef, useCallback, useState } from 'react';
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52621b1 and b6c1d2c.

📒 Files selected for processing (8)
  • packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/AppTitleBar/index.js (6 hunks)
  • packages/bruno-app/src/components/Dropdown/StyledWrapper.js (3 hunks)
  • packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (3 hunks)
  • packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/ui/ActionIcon/index.js (1 hunks)
  • packages/bruno-app/src/ui/MenuDropdown/index.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js
  • packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js
  • packages/bruno-app/src/ui/ActionIcon/index.js
  • packages/bruno-app/src/components/Dropdown/StyledWrapper.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js
  • packages/bruno-app/src/ui/MenuDropdown/index.js
  • packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js
  • packages/bruno-app/src/components/AppTitleBar/index.js
🧠 Learnings (4)
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : No trailing commas

Applied to files:

  • packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{jsx,tsx} : Styled Component CSS might also change layout but Tailwind classes shouldn't define colors

Applied to files:

  • packages/bruno-app/src/ui/MenuDropdown/index.js
  • packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{jsx,tsx} : Styled Components are used as wrappers to define both self and children components style; Tailwind classes are used specifically for layout based styles

Applied to files:

  • packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{jsx,tsx} : Use styled component's theme prop to manage CSS colors and not CSS variables when in the context of a styled component or any React component using the styled component

Applied to files:

  • packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js
🧬 Code graph analysis (2)
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (6)
packages/bruno-app/src/components/WorkspaceHome/index.js (1)
  • handleOpenCollection (65-71)
packages/bruno-app/src/components/ApiSpecPanel/index.js (1)
  • handleOpenApiSpec (39-43)
packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecItem/index.js (1)
  • handleOpenApiSpec (21-24)
packages/bruno-app/src/components/Sidebar/ApiSpecs/index.js (1)
  • handleOpenApiSpec (35-39)
packages/bruno-app/src/ui/ActionIcon/index.js (1)
  • ActionIcon (20-50)
packages/bruno-app/src/ui/MenuDropdown/index.js (1)
  • MenuDropdown (40-277)
packages/bruno-app/src/ui/MenuDropdown/index.js (1)
packages/bruno-app/src/components/Dropdown/index.js (1)
  • Dropdown (5-25)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Unit Tests
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: SSL Tests - macOS
🔇 Additional comments (5)
packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js (1)

92-97: Color reference updated correctly.

Using props.theme.text for the Bruno branding text aligns with the theme system. The previous theme.sidebar.muted was likely too subdued for branding text visibility.

packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (1)

261-275: Consider the label structure in dropdown items.

Line 265 adds an inline { type: 'label', label: 'Collections' } before spreading addDropdownItems. Since addDropdownItems already contains an "API Specs" label (line 208-210), the dropdown will show both labels. This seems intentional for categorization, but verify this is the desired UX.

packages/bruno-app/src/components/AppTitleBar/index.js (1)

185-245: ActionIcon usage is consistent and accessible.

Good adoption of ActionIcon across all controls with appropriate label props for accessibility and consistent size="lg" for titlebar actions.

packages/bruno-app/src/ui/MenuDropdown/index.js (2)

91-127: Keyboard navigation implementation is solid.

Proper handling of Escape, Enter/Space for activation, and Arrow/Home/End for navigation. Event propagation is correctly managed. Good accessibility pattern.


251-260: Child onClick composition correctly implemented.

The handler now properly calls any existing onClick on the child before handleTriggerClick(). This preserves consumer behavior.

Comment on lines +123 to +174
// Build workspace menu items
const workspaceMenuItems = useMemo(() => {
const items = sortedWorkspaces.map((workspace) => {
const isActive = workspace.uid === activeWorkspaceUid;
const isPinned = preferences?.workspaces?.pinnedWorkspaceUids?.includes(workspace.uid);

return {
id: workspace.uid,
label: toTitleCase(workspace.name),
onClick: () => handleWorkspaceSwitch(workspace.uid),
className: `workspace-item ${isActive ? 'active' : ''}`,
rightSection: (
<div className="workspace-actions">
{workspace.type !== 'default' && (
<ActionIcon
className={`pin-btn ${isPinned ? 'pinned' : ''}`}
onClick={(e) => handlePinWorkspace(workspace.uid, e)}
label={isPinned ? 'Unpin workspace' : 'Pin workspace'}
size="sm"
>
{isPinned ? (
<IconPinned size={14} stroke={1.5} />
) : (
<IconPin size={14} stroke={1.5} />
)}
</ActionIcon>
)}
{isActive && <IconCheck size={16} stroke={1.5} className="check-icon" />}
</div>
)
};
});

// Add label and action items
items.push(
{ type: 'label', label: 'Workspaces' },
{
id: 'create-workspace',
leftSection: IconPlus,
label: 'Create workspace',
onClick: handleCreateWorkspace
},
{
id: 'open-workspace',
leftSection: IconFolder,
label: 'Open workspace',
onClick: handleOpenWorkspace
}
);

return items;
}, [sortedWorkspaces, activeWorkspaceUid, preferences, handlePinWorkspace]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing dependencies in useMemo.

The workspaceMenuItems memo references handleWorkspaceSwitch, handleCreateWorkspace, and handleOpenWorkspace in onClick handlers, but these aren't in the dependency array. Since these functions aren't wrapped in useCallback, they're recreated each render, yet the memoized items won't update with them.

Either add them to dependencies or wrap those handlers in useCallback:

-  const workspaceMenuItems = useMemo(() => {
+  const handleWorkspaceSwitchCb = useCallback((workspaceUid) => {
+    dispatch(switchWorkspace(workspaceUid));
+    toast.success(`Switched to ${workspaces.find((w) => w.uid === workspaceUid)?.name}`);
+  }, [dispatch, workspaces]);

+  const handleCreateWorkspaceCb = useCallback(() => {
+    setCreateWorkspaceModalOpen(true);
+  }, []);

+  const handleOpenWorkspaceCb = useCallback(async () => {
+    try {
+      await dispatch(openWorkspaceDialog());
+      toast.success('Workspace opened successfully');
+    } catch (error) {
+      toast.error(error.message || 'Failed to open workspace');
+    }
+  }, [dispatch]);

   const workspaceMenuItems = useMemo(() => {
     // ... use the callback versions
-  }, [sortedWorkspaces, activeWorkspaceUid, preferences, handlePinWorkspace]);
+  }, [sortedWorkspaces, activeWorkspaceUid, preferences, handlePinWorkspace, handleWorkspaceSwitchCb, handleCreateWorkspaceCb, handleOpenWorkspaceCb]);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/bruno-app/src/components/AppTitleBar/index.js around lines 123 to
174, the useMemo that builds workspaceMenuItems references
handleWorkspaceSwitch, handleCreateWorkspace, and handleOpenWorkspace inside
onClick handlers but does not include them in the dependency array; either add
these functions to the useMemo dependency array or wrap each of them in
useCallback so their identities are stable and then include them in the
dependency list (update the final dependency array to [sortedWorkspaces,
activeWorkspaceUid, preferences, handlePinWorkspace, handleWorkspaceSwitch,
handleCreateWorkspace, handleOpenWorkspace] if you choose to add them).

Copy link
Contributor

@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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/collection/moving-requests/tag-persistence.spec.ts (1)

42-50: Reduce reliance on waitForTimeout.

This test uses page.waitForTimeout() excessively (9 occurrences). Per coding guidelines, these should be replaced with Playwright's built-in waiting mechanisms like expect() with visibility assertions or waitFor() on specific elements.

For example, instead of:

await page.waitForTimeout(200);
const tagInput = await page.getByTestId('tag-input').getByRole('textbox');

Use:

const tagInput = page.getByTestId('tag-input').getByRole('textbox');
await tagInput.waitFor({ state: 'visible' });

Additionally, consider structuring both tests with test.step blocks for better reporting and readability.

Also applies to: 99-107, 125-135, 200-200

♻️ Duplicate comments (1)
packages/bruno-app/src/ui/MenuDropdown/index.js (1)

139-172: selectedItemId focus behavior will be stale after initial mount

Because onDropdownCreate only runs when Tippy creates the instance, the onShow handler it registers captures whatever selectedItemId existed at that first render. Later prop changes won’t update the focus target, so menus may focus the wrong item when selectedItemId changes (e.g., active workspace switch).

You can keep focus behavior in sync by storing the ref in onDropdownCreate and moving the setProps({ onShow }) logic into an effect that depends on selectedItemId (and a memoized focusMenuItem):

-import { useRef, useCallback, useState } from 'react';
+import { useRef, useCallback, useState, useEffect } from 'react';
@@
-  const focusMenuItem = (item, addSelectedClass = false) => {
+  const focusMenuItem = useCallback((item, addSelectedClass = false) => {
     if (item) {
@@
-      item.scrollIntoView({ block: 'nearest' });
+      item.scrollIntoView({ block: 'nearest' });
     }
-  };
+  }, []);
@@
-  const onDropdownCreate = useCallback((ref) => {
-    tippyRef.current = ref;
-    if (ref) {
-      ref.setProps({
-        onShow: () => {
-          // Focus selected item if available, otherwise focus menu container
-          setTimeout(() => {
-            const menuContainer = ref.popper?.querySelector('[role="menu"]');
+  const onDropdownCreate = useCallback((ref) => {
+    tippyRef.current = ref;
+  }, []);
+
+  useEffect(() => {
+    const ref = tippyRef.current;
+    if (!ref) return;
+
+    ref.setProps({
+      onShow: () => {
+        setTimeout(() => {
+          const menuContainer = ref.popper?.querySelector('[role="menu"]');
             if (!menuContainer) return;
@@
-            menuContainer.focus();
-          }, 0);
-        }
-      });
-    }
-  }, [selectedItemId]);
+            menuContainer.focus();
+          }, 0);
+      }
+    });
+  }, [selectedItemId, focusMenuItem]);

This keeps the open-focus behavior aligned with the latest selectedItemId without recreating the Tippy instance.

🧹 Nitpick comments (8)
tests/import/wsdl/import-wsdl.spec.ts (1)

73-80: Optional: de-duplicate the “open import collection modal” interaction

This step is identical to the one in the XML test above. Consider pulling the “open import collection modal” interaction (including the collections-header-add-menu locator) into a small helper so future UI tweaks only need to be updated in one place. Not required for this PR, but it would reduce duplication and keep locators centralized.

tests/request/encoding/curl-encoding.spec.ts (1)

22-22: LGTM! Consider adding test.step for better reporting.

The test-id based selectors improve test stability. Both test cases would benefit from using test.step to structure the test flow (setup collection, create request, verify code generation), making the generated reports easier to read.

Also applies to: 68-68

tests/environments/import-environment/global-env-import.spec.ts (1)

11-11: LGTM! Recommend adding test.step for better organization.

The test-id based selector is correct. This test has multiple distinct phases (import collection, import environment, verify variables, test requests) that would benefit from test.step blocks for improved readability and better test reports.

tests/collection/open/open-multiple-collections.spec.ts (1)

99-99: Consider replacing with deterministic wait.

While this waitForTimeout was pre-existing, consider replacing it with a more deterministic wait condition to improve test reliability. For example, wait for the error toast element to be visible.

-    // Wait for error toasts to appear
-    await page.waitForTimeout(1000);
+    // Wait for error toasts to appear
+    const invalidCollectionError = page.getByText('The collection is not valid').first();
+    await expect(invalidCollectionError).toBeVisible({ timeout: 5000 });
tests/preferences/default-collection-location/default-collection-location.spec.js (1)

16-16: Consider replacing arbitrary timeouts with deterministic waits.

These waitForTimeout calls (pre-existing) could be replaced with specific condition waits to make tests more reliable and faster. As per coding guidelines, reduce usage of waitForTimeout unless absolutely necessary.

Also applies to: 34-34, 54-54, 72-72, 92-92

tests/collection/moving-tabs/move-tabs.spec.ts (1)

39-99: Consider reducing waitForTimeout usage.

The test uses multiple page.waitForTimeout() calls that could potentially be replaced with more robust expect() assertions with built-in waits. However, for drag-and-drop operations, some timeout usage may be unavoidable.

Example refactor for lines 39-40:

-    // Wait for the folder to be created and appear in the sidebar
-    await page.waitForTimeout(2000);
-    await expect(page.locator('.collection-item-name').filter({ hasText: 'test-folder' })).toBeVisible();
+    // Wait for the folder to be created and appear in the sidebar
+    await expect(page.locator('.collection-item-name').filter({ hasText: 'test-folder' })).toBeVisible({ timeout: 5000 });

As per coding guidelines, minimizing waitForTimeout usage improves test reliability.

packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (1)

249-287: Good use of ActionIcon + MenuDropdown for collections header

The refactor to ActionIcon triggers with MenuDropdown (including data-testid prefixes and clear label props for accessibility) is tidy and matches the new menu component’s contract. Keyboard and test selectors should work well with MenuDropdown’s internal behavior.

If you want, you could later extract the header menus into small subcomponents (e.g. CollectionsAddMenu, CollectionsActionsMenu) to keep SidebarHeader lean, but it’s not necessary right now.

packages/bruno-app/src/ui/MenuDropdown/index.js (1)

18-37: JSDoc doesn’t match current behavior for ariaLabel and per-item testId

The docblock still claims:

  • ariaLabel “falls back to label or title if not provided”
  • each item supports its own testId field

In practice:

  • renderMenuItem uses aria-label={item.ariaLabel} and title={item.title} with no fallback
  • the data-testid is always derived from the component-level testId plus item.id, and the testId item field is ignored

To avoid confusion for consumers, either:

  • implement the documented behavior (fallback label/title and optional per-item testId), or
  • simplify the JSDoc to match the actual API (ariaLabel is optional explicit override; test IDs are derived from the top-level data-testid and item.id only).

Given the recent cleanup in callers, updating the docs is probably enough here.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b6c1d2c and 9dbfacb.

📒 Files selected for processing (36)
  • packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (3 hunks)
  • packages/bruno-app/src/ui/MenuDropdown/index.js (1 hunks)
  • tests/collection/close-all-collections/close-all-collections.spec.ts (1 hunks)
  • tests/collection/create/create-collection.spec.ts (1 hunks)
  • tests/collection/moving-requests/tag-persistence.spec.ts (2 hunks)
  • tests/collection/moving-tabs/move-tabs.spec.ts (2 hunks)
  • tests/collection/open/open-multiple-collections.spec.ts (2 hunks)
  • tests/environments/import-environment/collection-env-import.spec.ts (1 hunks)
  • tests/environments/import-environment/global-env-import.spec.ts (1 hunks)
  • tests/import/bruno/import-bruno-corrupted-fails.spec.ts (1 hunks)
  • tests/import/bruno/import-bruno-missing-required-schema.spec.ts (1 hunks)
  • tests/import/bruno/import-bruno-with-examples.spec.ts (1 hunks)
  • tests/import/file-types/file-input-acceptance.spec.ts (1 hunks)
  • tests/import/file-types/invalid-file-handling.spec.ts (1 hunks)
  • tests/import/insomnia/import-insomnia-v4-environments.spec.ts (1 hunks)
  • tests/import/insomnia/import-insomnia-v5-environments.spec.ts (1 hunks)
  • tests/import/insomnia/invalid-missing-collection.spec.ts (1 hunks)
  • tests/import/insomnia/malformed-structure.spec.ts (1 hunks)
  • tests/import/openapi/duplicate-operation-names-fix.spec.ts (1 hunks)
  • tests/import/openapi/import-openapi-with-examples.spec.ts (2 hunks)
  • tests/import/openapi/malformed-yaml.spec.ts (1 hunks)
  • tests/import/openapi/missing-info.spec.ts (1 hunks)
  • tests/import/openapi/operation-name-with-newlines-fix.spec.ts (1 hunks)
  • tests/import/openapi/path-based-grouping.spec.ts (1 hunks)
  • tests/import/postman/import-postman-with-examples.spec.ts (1 hunks)
  • tests/import/postman/invalid-json.spec.ts (1 hunks)
  • tests/import/postman/invalid-missing-info.spec.ts (1 hunks)
  • tests/import/postman/invalid-schema.spec.ts (1 hunks)
  • tests/import/postman/malformed-structure.spec.ts (1 hunks)
  • tests/import/wsdl/import-wsdl.spec.ts (2 hunks)
  • tests/preferences/default-collection-location/default-collection-location.spec.js (1 hunks)
  • tests/request/encoding/curl-encoding.spec.ts (2 hunks)
  • tests/request/newlines/newlines-persistence.spec.ts (1 hunks)
  • tests/request/save/save.spec.ts (1 hunks)
  • tests/utils/page/actions.ts (2 hunks)
  • tests/utils/page/locators.ts (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • tests/collection/close-all-collections/close-all-collections.spec.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • tests/collection/moving-requests/tag-persistence.spec.ts
  • tests/import/bruno/import-bruno-with-examples.spec.ts
  • tests/import/postman/invalid-missing-info.spec.ts
  • tests/import/openapi/missing-info.spec.ts
  • tests/import/openapi/path-based-grouping.spec.ts
  • packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js
  • tests/import/postman/import-postman-with-examples.spec.ts
  • tests/import/openapi/malformed-yaml.spec.ts
  • tests/preferences/default-collection-location/default-collection-location.spec.js
  • tests/request/encoding/curl-encoding.spec.ts
  • tests/import/bruno/import-bruno-missing-required-schema.spec.ts
  • tests/import/file-types/invalid-file-handling.spec.ts
  • tests/import/insomnia/import-insomnia-v4-environments.spec.ts
  • tests/import/insomnia/malformed-structure.spec.ts
  • tests/import/postman/invalid-schema.spec.ts
  • tests/import/insomnia/import-insomnia-v5-environments.spec.ts
  • tests/collection/moving-tabs/move-tabs.spec.ts
  • tests/utils/page/actions.ts
  • tests/import/openapi/operation-name-with-newlines-fix.spec.ts
  • tests/collection/create/create-collection.spec.ts
  • tests/import/bruno/import-bruno-corrupted-fails.spec.ts
  • tests/import/file-types/file-input-acceptance.spec.ts
  • tests/utils/page/locators.ts
  • tests/collection/open/open-multiple-collections.spec.ts
  • tests/import/postman/malformed-structure.spec.ts
  • tests/import/wsdl/import-wsdl.spec.ts
  • tests/import/openapi/import-openapi-with-examples.spec.ts
  • tests/import/openapi/duplicate-operation-names-fix.spec.ts
  • tests/import/insomnia/invalid-missing-collection.spec.ts
  • tests/request/newlines/newlines-persistence.spec.ts
  • tests/environments/import-environment/global-env-import.spec.ts
  • tests/request/save/save.spec.ts
  • tests/environments/import-environment/collection-env-import.spec.ts
  • packages/bruno-app/src/ui/MenuDropdown/index.js
  • tests/import/postman/invalid-json.spec.ts
tests/**/**.*

⚙️ CodeRabbit configuration file

tests/**/**.*: Review the following e2e test code written using the Playwright test library. Ensure that:

  • Follow best practices for Playwright code and e2e automation

  • Try to reduce usage of page.waitForTimeout(); in code unless absolutely necessary and the locator cannot be found using existing expect() playwright calls

  • Avoid using page.pause() in code

  • Use locator variables for locators

  • Avoid using test.only

  • Use multiple assertions

  • Promote the use of test.step as much as possible so the generated reports are easier to read

  • Ensure that the fixtures like the collections are nested inside the fixtures folder

    Fixture Example*: Here's an example of possible fixture and test pair

    .
    ├── fixtures
    │   └── collection
    │       ├── base.bru
    │       ├── bruno.json
    │       ├── collection.bru
    │       ├── ws-test-request-with-headers.bru
    │       ├── ws-test-request-with-subproto.bru
    │       └── ws-test-request.bru
    ├── connection.spec.ts # <- Depends on the collection in ./fixtures/collection
    ├── headers.spec.ts
    ├── persistence.spec.ts
    ├── variable-interpolation
    │   ├── fixtures
    │   │   └── collection
    │   │       ├── environments
    │   │       ├── bruno.json
    │   │       └── ws-interpolation-test.bru
    │   ├── init-user-data
    │   └── variable-interpolation.spec.ts # <- Depends on the collection in ./variable-interpolation/fixtures/collection
    └── subproto.spec.ts
    

Files:

  • tests/collection/moving-requests/tag-persistence.spec.ts
  • tests/import/bruno/import-bruno-with-examples.spec.ts
  • tests/import/postman/invalid-missing-info.spec.ts
  • tests/import/openapi/missing-info.spec.ts
  • tests/import/openapi/path-based-grouping.spec.ts
  • tests/import/postman/import-postman-with-examples.spec.ts
  • tests/import/openapi/malformed-yaml.spec.ts
  • tests/preferences/default-collection-location/default-collection-location.spec.js
  • tests/request/encoding/curl-encoding.spec.ts
  • tests/import/bruno/import-bruno-missing-required-schema.spec.ts
  • tests/import/file-types/invalid-file-handling.spec.ts
  • tests/import/insomnia/import-insomnia-v4-environments.spec.ts
  • tests/import/insomnia/malformed-structure.spec.ts
  • tests/import/postman/invalid-schema.spec.ts
  • tests/import/insomnia/import-insomnia-v5-environments.spec.ts
  • tests/collection/moving-tabs/move-tabs.spec.ts
  • tests/utils/page/actions.ts
  • tests/import/openapi/operation-name-with-newlines-fix.spec.ts
  • tests/collection/create/create-collection.spec.ts
  • tests/import/bruno/import-bruno-corrupted-fails.spec.ts
  • tests/import/file-types/file-input-acceptance.spec.ts
  • tests/utils/page/locators.ts
  • tests/collection/open/open-multiple-collections.spec.ts
  • tests/import/postman/malformed-structure.spec.ts
  • tests/import/wsdl/import-wsdl.spec.ts
  • tests/import/openapi/import-openapi-with-examples.spec.ts
  • tests/import/openapi/duplicate-operation-names-fix.spec.ts
  • tests/import/insomnia/invalid-missing-collection.spec.ts
  • tests/request/newlines/newlines-persistence.spec.ts
  • tests/environments/import-environment/global-env-import.spec.ts
  • tests/request/save/save.spec.ts
  • tests/environments/import-environment/collection-env-import.spec.ts
  • tests/import/postman/invalid-json.spec.ts
🧠 Learnings (3)
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : No trailing commas

Applied to files:

  • packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.test.{js,jsx,ts,tsx} : Add tests for any new functionality or meaningful changes. If code is added, removed, or significantly modified, corresponding tests should be updated or created

Applied to files:

  • tests/request/newlines/newlines-persistence.spec.ts
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{jsx,tsx} : Styled Component CSS might also change layout but Tailwind classes shouldn't define colors

Applied to files:

  • packages/bruno-app/src/ui/MenuDropdown/index.js
🧬 Code graph analysis (1)
packages/bruno-app/src/ui/MenuDropdown/index.js (1)
packages/bruno-app/src/components/Dropdown/index.js (1)
  • Dropdown (5-25)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: Unit Tests
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: CLI Tests
🔇 Additional comments (38)
tests/import/wsdl/import-wsdl.spec.ts (1)

15-22: Selector change to use test-id based trigger looks good

Using page.getByTestId('collections-header-add-menu') is more robust than the previous class-based selector and aligns with the new ActionIcon/MenuDropdown UI pattern and test-id strategy used elsewhere in the suite. I don’t see any behavioural risks with this change.

tests/request/save/save.spec.ts (1)

12-12: LGTM! Test-id based selector improves test stability.

Replacing the CSS class selector with a test-id based selector makes the test more resilient to UI changes and aligns with testing best practices.

tests/import/insomnia/import-insomnia-v5-environments.spec.ts (1)

26-26: LGTM! Excellent test structure.

The test-id based selector improves maintainability. The comprehensive use of test.step throughout this test is exemplary and makes the generated reports highly readable.

tests/import/openapi/operation-name-with-newlines-fix.spec.ts (1)

15-15: LGTM! Test-id selector improves maintainability.

The change from CSS class to test-id based selector is correct and makes the test more resilient to UI refactoring.

tests/import/insomnia/invalid-missing-collection.spec.ts (1)

8-8: LGTM! Test-id selector enhances test reliability.

The change to a test-id based selector is correct and improves test stability.

tests/collection/moving-requests/tag-persistence.spec.ts (1)

12-12: LGTM! Test-id selectors improve test stability.

The change from CSS class to test-id based selectors is correct and aligns with best practices.

Also applies to: 82-82

tests/collection/create/create-collection.spec.ts (1)

11-11: LGTM! Test-id selector improves test maintainability.

The change to a test-id based selector is correct and makes the test more resilient to UI changes.

tests/import/file-types/invalid-file-handling.spec.ts (1)

8-9: Selector change to getByTestId looks good

Switching to page.getByTestId('collections-header-add-menu').click() makes this step more robust and aligned with the new header UI, without altering the flow of the test.

tests/import/postman/invalid-schema.spec.ts (1)

8-9: Consistent use of collections-header-add-menu

Using getByTestId('collections-header-add-menu') here keeps this Postman import test in sync with the rest of the suite and avoids brittle class selectors.

tests/environments/import-environment/collection-env-import.spec.ts (1)

15-16: Header add menu trigger updated appropriately

The switch to page.getByTestId('collections-header-add-menu').click() is aligned with the refactored header/menu components and makes this environment-import flow less dependent on CSS classes. The rest of the import steps remain intact.

tests/import/bruno/import-bruno-with-examples.spec.ts (1)

13-16: Stable trigger inside test.step

Updating the “Open import collection modal” step to use getByTestId('collections-header-add-menu') keeps the step semantics the same while improving selector stability and consistency with other specs.

tests/import/openapi/missing-info.spec.ts (1)

8-9: Uniform add-menu selector for OpenAPI missing-info test

Using page.getByTestId('collections-header-add-menu') here keeps this OpenAPI test aligned with the rest of the import suite and reduces coupling to presentational classes.

tests/import/postman/invalid-missing-info.spec.ts (1)

8-9: Postman “missing info” test now uses stable add-menu trigger

The move to getByTestId('collections-header-add-menu') matches the invalid-schema Postman test and the broader suite, improving resilience to UI refactors.

tests/request/newlines/newlines-persistence.spec.ts (1)

13-17: Creation flow updated to use header add-menu test ID

Clicking page.getByTestId('collections-header-add-menu') before choosing “Create collection” keeps this persistence test aligned with the new header UI while avoiding brittle class-based selectors; no extra waits or timeouts were introduced.

tests/import/openapi/duplicate-operation-names-fix.spec.ts (1)

15-16: Import start step now uses test-id based selector

Starting the import via getByTestId('collections-header-add-menu') is consistent with other OpenAPI/import tests and should be more resilient to future UI/style changes.

tests/collection/open/open-multiple-collections.spec.ts (1)

60-60: LGTM – Test-id selector improves test stability.

The migration from CSS class selector to test-id based selector is a solid improvement for test maintainability and resilience to UI changes.

Also applies to: 95-95

tests/preferences/default-collection-location/default-collection-location.spec.js (1)

59-60: LGTM – Test-id selector improves test maintainability.

tests/import/openapi/import-openapi-with-examples.spec.ts (1)

38-39: LGTM – Excellent use of test-id selector within test.step.

The test-id based selector is more resilient to UI changes, and the test.step structure makes reports easy to read.

Also applies to: 155-156

tests/import/openapi/path-based-grouping.spec.ts (1)

15-16: LGTM – Test-id selector is the right approach.

tests/import/openapi/malformed-yaml.spec.ts (1)

8-9: LGTM – Consistent with test-id migration pattern.

tests/import/bruno/import-bruno-corrupted-fails.spec.ts (1)

8-9: LGTM – Test-id selector improves test resilience.

tests/import/postman/malformed-structure.spec.ts (1)

8-9: LGTM – Test-id selector follows best practices.

tests/import/postman/invalid-json.spec.ts (1)

8-9: LGTM – Completes the test-id migration consistently.

tests/import/file-types/file-input-acceptance.spec.ts (1)

5-5: LGTM! Selector update aligns with UI refactor.

The change from CSS class selector to test-id based selector (collections-header-add-menu) improves test reliability and aligns with the broader refactor introducing MenuDropdown and ActionIcon components.

tests/import/postman/import-postman-with-examples.spec.ts (1)

38-38: LGTM! Excellent test structure.

The selector update is correct and the test demonstrates excellent use of test.step blocks and multiple assertions per step, aligning with Playwright best practices.

tests/import/insomnia/import-insomnia-v4-environments.spec.ts (1)

26-26: LGTM! Well-structured test.

The selector change is correct and the test demonstrates excellent structure with comprehensive test.step blocks and detailed assertions for environment validation.

tests/collection/moving-tabs/move-tabs.spec.ts (2)

12-12: LGTM! Selector update is consistent.

The change from CSS class selector to test-id based selector is correct and aligns with the UI refactor.


103-103: LGTM! Selector update is consistent.

The change is correct and matches the update in the first test.

tests/import/bruno/import-bruno-missing-required-schema.spec.ts (1)

8-8: LGTM! Selector update is correct.

The change to test-id based selector improves test maintainability and aligns with the UI component refactor.

tests/import/insomnia/malformed-structure.spec.ts (1)

8-8: LGTM! Selector update is correct.

The change to test-id based selector is consistent with other test updates in this PR.

tests/utils/page/actions.ts (2)

67-67: LGTM! Centralized selector update.

The change to test-id based selector in the createCollection helper function is correct and will propagate the update to all tests using this utility. This centralized approach improves maintainability.


187-187: LGTM! Centralized selector update.

The change to test-id based selector in the importCollection helper function is consistent with the createCollection update and ensures uniform test behavior across the suite.

tests/utils/page/locators.ts (2)

20-20: LGTM! Locator update aligns with UI restructuring.

The change from 'close-all-collections-button' to 'collections-header-actions-menu-close-all' reflects the new MenuDropdown structure in the SidebarHeader component. The more specific test-id naming is an improvement.


84-84: LGTM! Locator definition is consistent.

The change from CSS class selector to test-id based selector ('collections-header-add-menu') is correct and matches the updates throughout the test suite. Centralizing this in the locator builder is good practice.

packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (1)

181-247: Dropdown item configs look consistent with MenuDropdown API

The addDropdownItems and actionsDropdownItems arrays have clean, consistent item shapes (ids, type: 'label', icons as components, no trailing commas) and map correctly onto MenuDropdown’s expectations for item.id, item.type, item.label, and item.onClick. No functional or style issues from this block.

packages/bruno-app/src/ui/MenuDropdown/index.js (3)

9-127: Keyboard navigation logic is solid and covers common menu patterns

getNextIndex plus handleMenuKeyDown give complete Arrow/Home/End/Escape handling, including action keys and roving focus from the menu container. The implementation looks correct and straightforward.


188-230: Menu item rendering and a11y attributes look good

renderMenuItem, renderLabel, and renderDivider apply appropriate roles (menuitem, presentation, separator), disabled handling (aria-disabled, tabIndex), and label text, and they avoid trailing commas per guidelines. The right-section click guard is a nice touch to keep parent item handlers from firing unintentionally.


251-275: Trigger cloning correctly preserves caller onClick and test IDs

The trigger element cloning now composes the child’s existing onClick with handleTriggerClick and consistently applies data-testid from the component prop. This avoids the earlier pitfall of overriding consumer handlers and should integrate cleanly with existing tests.

tabIndex={item.disabled ? -1 : 0}
aria-label={resolvedLabel}
aria-disabled={item.disabled}
title={resolvedLabel}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Don't show title when it is same as the label

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