refactor: update AppTitleBar and SidebarHeader components#6341
refactor: update AppTitleBar and SidebarHeader components#6341bijin-bruno merged 2 commits intousebruno:mainfrom
Conversation
WalkthroughReplaces 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
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()
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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.
itemIndexis 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: MoveWorkspaceNameoutside the component or memoize it.Defining
forwardRefcomponents 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
!importantdeclarations incolorOnHoverensure 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
ariaLabelbut 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 standardaria-labelattribute.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 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()notfunc ()
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.jspackages/bruno-app/src/ui/MenuDropdown/index.jspackages/bruno-app/src/ui/ActionIcon/index.jspackages/bruno-app/src/ui/ActionIcon/StyledWrapper.jspackages/bruno-app/src/components/Dropdown/StyledWrapper.jspackages/bruno-app/src/components/AppTitleBar/StyledWrapper.jspackages/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.jspackages/bruno-app/src/ui/ActionIcon/StyledWrapper.jspackages/bruno-app/src/components/Dropdown/StyledWrapper.jspackages/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.jspackages/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.jspackages/bruno-app/src/components/Dropdown/StyledWrapper.jspackages/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-focusedclass managed by MenuDropdown's keyboard navigation.
72-74: LGTM! Flex layout additions for dropdown item structure.
.dropdown-labelwithflex: 1allows labels to fill available space, while.dropdown-right-sectionpositions trailing elements (e.g., pin buttons, check icons) consistently. Aligns with MenuDropdown'srenderMenuItemoutput.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.mutedtotheme.textmakes 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, andaria-hiddenattributes. 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
scrollIntoViewensures visibility.
142-176: LGTM! Focus initialization on dropdown open.The
onShowhook correctly focuses theselectedItemIditem when provided, falling back to the menu container. ThesetTimeout(..., 0)ensures the DOM is ready.packages/bruno-app/src/components/AppTitleBar/index.js (3)
123-174: LGTM! Well-structured workspace menu items.The
useMemocorrectly memoizes the menu items with appropriate dependencies. The item structure withrightSectioncontaining 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
asprop, proper $ prefixing for transient props, and good accessibility attributes by setting bothtitleandaria-labelfrom the resolved label.
There was a problem hiding this comment.
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
requestAnimationFramefor 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
📒 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()notfunc ()
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
cloneElementcall now correctly composes the child's existingonClickhandler withhandleTriggerClick, 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; |
There was a problem hiding this comment.
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.
| 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.
There was a problem hiding this comment.
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-labelonly usesitem.ariaLabeldirectly. If a consumer provides onlylabelortitle, 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
ariaLabelis undefined and no fallback is provided, consider omitting the attribute entirely to avoid renderingaria-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.$sizedirectly if it's not insizeMap. This allows custom numeric sizes but could produce invalid CSS (e.g.,undefinedpxorinvalidpx) 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: WrapfocusMenuIteminuseCallback.This function is referenced inside
handleMenuKeyDownandonDropdownCreatebut 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
focusMenuItemto the dependency arrays ofhandleMenuKeyDown(line 126) andonDropdownCreate(line 171).
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 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()notfunc ()
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.jspackages/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
onClickhandler before callinghandleTriggerClick. This preserves any existing click behavior on the trigger element.
260-275: LGTM!Clean integration with the underlying Dropdown component. Controlled visibility via
isOpenstate and proper outside-click handling.
There was a problem hiding this comment.
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 wiredThe
addDropdownItemsandactionsDropdownItemsarrays 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 inMenuDropdown, these arrays would be good candidates foruseMemo, but that’s an optional micro-optimization for now.
224-257: ActionIcon + MenuDropdown wiring for collections actions is solidSearch, add, and “more actions” are all correctly routed through existing handlers, and the
ActionIconlabels plusaria-hiddenicons give you good accessibility semantics. If you later surface a “search is open” boolean from the parent, it may be worth threading that intoActionIcon(e.g., as anactive/aria-pressed-like state), but that’s purely a UX refinement.
272-305: ARIA updates for the view switcher improve semantics; consider full tabs patternUsing
role="heading"for the single view andtablist/tab/aria-selectedon the multi-view buttons is a nice accessibility upgrade, and icons beingaria-hiddenavoids duplicate announcements. If the associated panels aren’t already wired elsewhere, you may want to pair each tab with a correspondingrole="tabpanel"element whoseidmatchesaria-controlsto fully align with the WAI-ARIA tabs pattern.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 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()notfunc ()
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 foruseState,MenuDropdown, andActionIconlook correctThe new imports are clean and correctly scoped to support the refactor to shared UI components.
6bd16c3 to
20dbdd5
Compare
There was a problem hiding this comment.
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 expectationsThe JSDoc says
labelis used for both title and aria-label (preferred) and thatariaLabel/titlefall back accordingly, butrenderMenuItemcurrently wires these independently:
- Visible text:
item.labelaria-label:item.ariaLabeltitle:item.titleSo items with only
labeldon’t get an explicit aria-label/title, and items with onlytitledon’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
ariaLabelhighest priority, falls back tolabel/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 totype="button"When
componentis 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
typewhen 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 andworkspaceMenuItemsdependenciesFunctionally this works, but a couple of small refactors would make it cleaner and more lint-friendly:
WorkspaceNameis created withforwardRefinsideAppTitleBar, so a new component type is allocated every render. Moving it outside the component (or wrapping theforwardRefcall in auseMemo([])) would stabilize its identity.workspaceMenuItems’suseMemodepends onsortedWorkspaces,activeWorkspaceUid,preferences, andhandlePinWorkspace, but it also closes overhandleWorkspaceSwitch,handleCreateWorkspace, andhandleOpenWorkspace. They’re stable enough in practice, but strict hook linting would either ask to add them as dependencies (or wrap them inuseCallback) or to dropuseMemoif 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
📒 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()notfunc ()
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.jspackages/bruno-app/src/components/Sidebar/SidebarHeader/index.jspackages/bruno-app/src/components/Dropdown/StyledWrapper.jspackages/bruno-app/src/components/AppTitleBar/StyledWrapper.jspackages/bruno-app/src/components/AppTitleBar/index.jspackages/bruno-app/src/ui/MenuDropdown/index.jspackages/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.jspackages/bruno-app/src/components/Dropdown/StyledWrapper.jspackages/bruno-app/src/components/AppTitleBar/StyledWrapper.jspackages/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.jspackages/bruno-app/src/components/Dropdown/StyledWrapper.jspackages/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 usagesScoping the
padding: 8px 4px 6px 10px;to.sidebar-headerinstead of the rootStyledWrappershould 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 goodSwitching
.bruno-texttotheme.textaligns with styled-components theming guidance and should improve contrast against the sidebar background.
| [role="menu"] { | ||
| outline: none; | ||
| &:focus { | ||
| outline: none; | ||
| } | ||
| &:focus-visible { | ||
| outline: none; | ||
| } | ||
| } |
There was a problem hiding this comment.
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).
| /** | ||
| * 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 | ||
| */ |
There was a problem hiding this comment.
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
92a36cc to
b6c1d2c
Compare
There was a problem hiding this comment.
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 stylesThe divider is rendered with
className="dropdown-divider". Previous styling incomponents/Dropdown/StyledWrapper.jstargeted.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
addDropdownItemsandactionsDropdownItemsare 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 forMenuDropdownreconciliation.You could wrap these arrays in
useMemokeyed 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 defaultworkspace-name-containerclass when spreading propsSpreading
propsafterclassName="workspace-name-container"means any incomingclassNamewill 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: EnsureworkspaceMenuItemsuseMemo stays aligned with hooks lint and handlersThe
workspaceMenuItemsconstruction is solid (active checks, pin/unpin right section, workspace actions appended), but theuseMemodependency 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
useCallbackand include them in the dependency array, or- Drop
useMemoand 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: removeEscapefrom NAVIGATION_KEYS to reflect actual behavior
Escapeis included inNAVIGATION_KEYS, but it is handled separately and returns early inhandleMenuKeyDown. As a result, it never reaches the navigation block, and having it inNAVIGATION_KEYSis 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
📒 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()notfunc ()
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.jspackages/bruno-app/src/ui/MenuDropdown/index.jspackages/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 consistentThe 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 goodThe updated
cloneElementcorrectly composes the child’s existingonClickwithhandleTriggerClick, and also assigns a consistentdata-testidto 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 onlabelfor tooltips but may lack explicit aria-labelsThe new
ActionIconusages (Home, toggle sidebar, devtools, layout) passlabelbut may not include explicitaria-labelprops. Verify theActionIconcomponent implementation to confirm how it handles thelabelprop for accessibility purposes, and ensure explicitaria-labelattributes are added to all icon-only button usages if the component doesn't automatically maplabeltoaria-label.
| // 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]); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n packages/bruno-app/src/ui/MenuDropdown/index.js | head -200Repository: 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.
There was a problem hiding this comment.
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: MoveWorkspaceNameoutside the component.Defining
forwardRefinside the render function creates a new component identity on every render, potentially causing unnecessary re-mounts. Move it outsideAppTitleBaror useuseMemoto 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 foraria-labelaccessibility.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
addDropdownItemsis well-organized. Each item has a uniqueid, appropriate icon vialeftSection, 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
📒 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()notfunc ()
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.jspackages/bruno-app/src/ui/MenuDropdown/index.jspackages/bruno-app/src/components/AppTitleBar/StyledWrapper.jspackages/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.jspackages/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.textfor the Bruno branding text aligns with the theme system. The previoustheme.sidebar.mutedwas 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 spreadingaddDropdownItems. SinceaddDropdownItemsalready 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
ActionIconacross all controls with appropriatelabelprops for accessibility and consistentsize="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
onClickon the child beforehandleTriggerClick(). This preserves consumer behavior.
| // 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]); |
There was a problem hiding this comment.
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).
…istency and improved readability
There was a problem hiding this comment.
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 likeexpect()with visibility assertions orwaitFor()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.stepblocks 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:selectedItemIdfocus behavior will be stale after initial mountBecause
onDropdownCreateonly runs when Tippy creates the instance, theonShowhandler it registers captures whateverselectedItemIdexisted at that first render. Later prop changes won’t update the focus target, so menus may focus the wrong item whenselectedItemIdchanges (e.g., active workspace switch).You can keep focus behavior in sync by storing the ref in
onDropdownCreateand moving thesetProps({ onShow })logic into an effect that depends onselectedItemId(and a memoizedfocusMenuItem):-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
selectedItemIdwithout 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” interactionThis step is identical to the one in the XML test above. Consider pulling the “open import collection modal” interaction (including the
collections-header-add-menulocator) 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.stepto 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.stepblocks 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
waitForTimeoutwas 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
waitForTimeoutcalls (pre-existing) could be replaced with specific condition waits to make tests more reliable and faster. As per coding guidelines, reduce usage ofwaitForTimeoutunless 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 reducingwaitForTimeoutusage.The test uses multiple
page.waitForTimeout()calls that could potentially be replaced with more robustexpect()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
waitForTimeoutusage improves test reliability.packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (1)
249-287: Good use of ActionIcon + MenuDropdown for collections headerThe refactor to
ActionIcontriggers withMenuDropdown(includingdata-testidprefixes and clearlabelprops 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 forariaLabeland per-itemtestIdThe docblock still claims:
ariaLabel“falls back to label or title if not provided”- each item supports its own
testIdfieldIn practice:
renderMenuItemusesaria-label={item.ariaLabel}andtitle={item.title}with no fallback- the
data-testidis always derived from the component-leveltestIdplusitem.id, and thetestIditem field is ignoredTo 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-testidanditem.idonly).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
📒 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()notfunc ()
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.tstests/import/bruno/import-bruno-with-examples.spec.tstests/import/postman/invalid-missing-info.spec.tstests/import/openapi/missing-info.spec.tstests/import/openapi/path-based-grouping.spec.tspackages/bruno-app/src/components/Sidebar/SidebarHeader/index.jstests/import/postman/import-postman-with-examples.spec.tstests/import/openapi/malformed-yaml.spec.tstests/preferences/default-collection-location/default-collection-location.spec.jstests/request/encoding/curl-encoding.spec.tstests/import/bruno/import-bruno-missing-required-schema.spec.tstests/import/file-types/invalid-file-handling.spec.tstests/import/insomnia/import-insomnia-v4-environments.spec.tstests/import/insomnia/malformed-structure.spec.tstests/import/postman/invalid-schema.spec.tstests/import/insomnia/import-insomnia-v5-environments.spec.tstests/collection/moving-tabs/move-tabs.spec.tstests/utils/page/actions.tstests/import/openapi/operation-name-with-newlines-fix.spec.tstests/collection/create/create-collection.spec.tstests/import/bruno/import-bruno-corrupted-fails.spec.tstests/import/file-types/file-input-acceptance.spec.tstests/utils/page/locators.tstests/collection/open/open-multiple-collections.spec.tstests/import/postman/malformed-structure.spec.tstests/import/wsdl/import-wsdl.spec.tstests/import/openapi/import-openapi-with-examples.spec.tstests/import/openapi/duplicate-operation-names-fix.spec.tstests/import/insomnia/invalid-missing-collection.spec.tstests/request/newlines/newlines-persistence.spec.tstests/environments/import-environment/global-env-import.spec.tstests/request/save/save.spec.tstests/environments/import-environment/collection-env-import.spec.tspackages/bruno-app/src/ui/MenuDropdown/index.jstests/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 existingexpect()playwright callsAvoid using
page.pause()in codeUse locator variables for locators
Avoid using test.only
Use multiple assertions
Promote the use of
test.stepas much as possible so the generated reports are easier to readEnsure that the
fixtureslike the collections are nested inside thefixturesfolderFixture 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.tstests/import/bruno/import-bruno-with-examples.spec.tstests/import/postman/invalid-missing-info.spec.tstests/import/openapi/missing-info.spec.tstests/import/openapi/path-based-grouping.spec.tstests/import/postman/import-postman-with-examples.spec.tstests/import/openapi/malformed-yaml.spec.tstests/preferences/default-collection-location/default-collection-location.spec.jstests/request/encoding/curl-encoding.spec.tstests/import/bruno/import-bruno-missing-required-schema.spec.tstests/import/file-types/invalid-file-handling.spec.tstests/import/insomnia/import-insomnia-v4-environments.spec.tstests/import/insomnia/malformed-structure.spec.tstests/import/postman/invalid-schema.spec.tstests/import/insomnia/import-insomnia-v5-environments.spec.tstests/collection/moving-tabs/move-tabs.spec.tstests/utils/page/actions.tstests/import/openapi/operation-name-with-newlines-fix.spec.tstests/collection/create/create-collection.spec.tstests/import/bruno/import-bruno-corrupted-fails.spec.tstests/import/file-types/file-input-acceptance.spec.tstests/utils/page/locators.tstests/collection/open/open-multiple-collections.spec.tstests/import/postman/malformed-structure.spec.tstests/import/wsdl/import-wsdl.spec.tstests/import/openapi/import-openapi-with-examples.spec.tstests/import/openapi/duplicate-operation-names-fix.spec.tstests/import/insomnia/invalid-missing-collection.spec.tstests/request/newlines/newlines-persistence.spec.tstests/environments/import-environment/global-env-import.spec.tstests/request/save/save.spec.tstests/environments/import-environment/collection-env-import.spec.tstests/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 goodUsing
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.stepthroughout 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 togetByTestIdlooks goodSwitching 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 ofcollections-header-add-menuUsing
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 appropriatelyThe 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 insidetest.stepUpdating 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 testUsing
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 triggerThe 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 IDClicking
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 selectorStarting 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.stepblocks 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.stepblocks 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
createCollectionhelper 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
importCollectionhelper function is consistent with thecreateCollectionupdate 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 APIThe
addDropdownItemsandactionsDropdownItemsarrays have clean, consistent item shapes (ids,type: 'label', icons as components, no trailing commas) and map correctly onto MenuDropdown’s expectations foritem.id,item.type,item.label, anditem.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
getNextIndexplushandleMenuKeyDowngive 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, andrenderDividerapply 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 calleronClickand test IDsThe trigger element cloning now composes the child’s existing
onClickwithhandleTriggerClickand consistently appliesdata-testidfrom 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} |
There was a problem hiding this comment.
Don't show title when it is same as the label
Description
Update AppTitleBar and SidebarHeader components to use MenuDropdown and ActionIcon for improved UI consistency
Contribution Checklist:
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
Style
Refactor
Tests
✏️ Tip: You can customize this high-level summary in your review settings.