feat: Moved Workspace Selector to the Titlebar of the window.#6319
feat: Moved Workspace Selector to the Titlebar of the window.#6319bijin-bruno merged 6 commits intousebruno:mainfrom
Conversation
… SidebarHeader, and enhance collections search functionality
WalkthroughAdds a draggable 36px AppTitleBar and integrates it into the main layout; replaces Sidebar TitleBar with a new SidebarHeader (tabbed), refactors sidebar/collections search and styling, introduces OS-specific Electron titlebar options and fullscreen IPC, adds UI components/utilities, and updates tests/selectors. Changes
Sequence Diagram(s)sequenceDiagram
participant Renderer as Renderer (React)
participant AppTitleBar as AppTitleBar Component
participant Redux as Redux Store
participant Main as Electron Main
Renderer->>Redux: read workspaces, activeWorkspace, preferences
Renderer->>AppTitleBar: mount / render
AppTitleBar->>Redux: dispatch switchWorkspace / savePreferences / toggleUI
AppTitleBar->>Main: IPC -> open-workspace / toggle-devtools
Main-->>AppTitleBar: IPC -> enter-full-screen / leave-full-screen
AppTitleBar->>Renderer: apply .fullscreen class / update UI state
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (9)
packages/bruno-app/src/providers/ReduxStore/slices/app.js (1)
8-8: Sidebar width increased to accommodate new layout.The initial width bump from 222px to 250px aligns with the AppTitleBar integration. Consider extracting layout constants (like
INITIAL_SIDEBAR_WIDTH) for easier future maintenance, though this is optional.tests/collection/draft/draft-indicator.spec.ts (1)
149-149: Good switch to test-id selector.Using
getByTestId('add-client-cert')is more robust than role-based lookup when multiple "Add" buttons exist on the page.Per coding guidelines, consider extracting to a locator variable for clarity:
const addClientCertBtn = page.getByTestId('add-client-cert'); await addClientCertBtn.click();This is optional given it's a single use.
packages/bruno-app/src/components/Sidebar/Collections/index.js (1)
1-1: Consolidate React imports.The
useMemoimport should be combined with the existing React import on line 1 for consistency.Apply this diff:
-import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import { useSelector } from 'react-redux'; import Collection from './Collection'; import CreateCollection from '../CreateCollection'; import StyledWrapper from './StyledWrapper'; import CreateOrOpenCollection from './CreateOrOpenCollection'; import CollectionSearch from './CollectionSearch/index'; -import { useMemo } from 'react';Also applies to: 8-8
packages/bruno-app/src/components/Sidebar/Collections/StyledWrapper.js (1)
30-32: Remove redundant hover state.The scrollbar thumb hover state applies the same color as the default state, making it redundant.
Apply this diff:
&::-webkit-scrollbar-thumb { background: ${(props) => props.theme.scrollbar.color}; border-radius: 3px; } - - &::-webkit-scrollbar-thumb:hover { - background: ${(props) => props.theme.scrollbar.color}; - } }packages/bruno-app/src/components/Sidebar/Collections/CollectionSearch/index.js (1)
1-29: LGTM! Clean refactor to use StyledWrapper.The component structure aligns well with the StyledWrapper CSS classes. One minor accessibility consideration: the clear icon
divat line 21 lacks keyboard accessibility (notabIndex,role, oronKeyDown). Consider using a<button>element instead for better a11y.- <div className="clear-icon" onClick={() => setSearchText('')}> - <IconX size={14} strokeWidth={1.5} /> - </div> + <button + type="button" + className="clear-icon" + onClick={() => setSearchText('')} + aria-label="Clear search" + > + <IconX size={14} strokeWidth={1.5} /> + </button>packages/bruno-electron/src/index.js (1)
81-82: Consider reusingisMacfromutils/arch.js.
isMacis already defined inpackages/bruno-electron/src/utils/arch.js(line 1). Reusing it would avoid duplication. However,isWindowsis new and would need to be added to that utility if you choose to consolidate.-const isMac = process.platform === 'darwin'; -const isWindows = process.platform === 'win32'; +const { isMac } = require('./utils/arch'); +const isWindows = process.platform === 'win32';Alternatively, extend
utils/arch.jswithisWindowsand import both.packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (1)
43-44: Consider combining these selectors.Two consecutive
useSelectorcalls access the samestate.collectionsslice. You can merge them into a single selector for minor optimization.- const { collections } = useSelector((state) => state.collections); - const { collectionSortOrder } = useSelector((state) => state.collections); + const { collections, collectionSortOrder } = useSelector((state) => state.collections);packages/bruno-app/src/components/AppTitleBar/index.js (2)
56-57: Unused ref and callback - dead code.
workspaceDropdownTippyRefis assigned but never read. Either remove it or implement the intended functionality (e.g., programmatically closing the dropdown).- const workspaceDropdownTippyRef = useRef(); - const onWorkspaceDropdownCreate = (ref) => (workspaceDropdownTippyRef.current = ref);If you need programmatic control, retain and use it; otherwise, remove to reduce clutter.
69-76: Component defined inside parent causes unnecessary re-renders.
WorkspaceNameis recreated on every render since it's defined insideAppTitleBar. This breaks React's reconciliation and can cause focus/state issues. Wrap withuseMemoor extract outside the component.- const WorkspaceName = forwardRef((props, ref) => { - return ( - <div ref={ref} className="workspace-name-container" onClick={() => setShowWorkspaceDropdown(!showWorkspaceDropdown)}> - <span className="workspace-name">{toTitleCase(activeWorkspace?.name) || 'Default Workspace'}</span> - <IconChevronDown size={14} stroke={1.5} className="chevron-icon" /> - </div> - ); - }); + const WorkspaceName = useMemo( + () => + forwardRef((props, ref) => ( + <div ref={ref} className="workspace-name-container" onClick={() => setShowWorkspaceDropdown((prev) => !prev)}> + <span className="workspace-name">{toTitleCase(activeWorkspace?.name) || 'Default Workspace'}</span> + <IconChevronDown size={14} stroke={1.5} className="chevron-icon" /> + </div> + )), + [activeWorkspace?.name] + );Also add
useMemoto imports on line 2.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (22)
packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js(1 hunks)packages/bruno-app/src/components/AppTitleBar/index.js(1 hunks)packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js(1 hunks)packages/bruno-app/src/components/Icons/IconBottombarToggle/index.js(1 hunks)packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js(2 hunks)packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js(1 hunks)packages/bruno-app/src/components/Sidebar/Collections/CollectionSearch/StyledWrapper.js(1 hunks)packages/bruno-app/src/components/Sidebar/Collections/CollectionSearch/index.js(1 hunks)packages/bruno-app/src/components/Sidebar/Collections/StyledWrapper.js(1 hunks)packages/bruno-app/src/components/Sidebar/Collections/index.js(3 hunks)packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js(1 hunks)packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js(1 hunks)packages/bruno-app/src/components/Sidebar/StyledWrapper.js(1 hunks)packages/bruno-app/src/components/Sidebar/TitleBar/StyledWrapper.js(0 hunks)packages/bruno-app/src/components/Sidebar/TitleBar/index.js(0 hunks)packages/bruno-app/src/components/Sidebar/index.js(2 hunks)packages/bruno-app/src/components/WorkspaceHome/index.js(1 hunks)packages/bruno-app/src/pages/Bruno/index.js(3 hunks)packages/bruno-app/src/providers/App/index.js(1 hunks)packages/bruno-app/src/providers/ReduxStore/slices/app.js(1 hunks)packages/bruno-electron/src/index.js(3 hunks)tests/collection/draft/draft-indicator.spec.ts(1 hunks)
💤 Files with no reviewable changes (2)
- packages/bruno-app/src/components/Sidebar/TitleBar/index.js
- packages/bruno-app/src/components/Sidebar/TitleBar/StyledWrapper.js
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CODING_STANDARDS.md)
**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation, never tabs
Use single quotes for strings instead of double quotes
Always add semicolons at the end of statements
No trailing commas in code
Always use parentheses around parameters in arrow functions, even for single parameters
For multiline constructs, put opening braces on the same line with a minimum of 2 elements for multiline formatting
No newlines inside function parentheses
Space before and after the arrow in arrow functions:() => {}
No space between function name and parentheses:func()notfunc ()
Semicolons should go at the end of the line, not on a new line
Function names should be concise and descriptive
Add JSDoc comments to provide details to abstractions
Avoid single-line abstractions where all that is being done is increasing the call stack with one additional function
Add meaningful comments to explain complex code flow instead of obvious comments
Files:
packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.jspackages/bruno-app/src/components/Sidebar/SidebarHeader/index.jspackages/bruno-app/src/providers/ReduxStore/slices/app.jspackages/bruno-app/src/components/Sidebar/Collections/CollectionSearch/StyledWrapper.jspackages/bruno-app/src/components/AppTitleBar/StyledWrapper.jspackages/bruno-app/src/components/Sidebar/Collections/StyledWrapper.jspackages/bruno-app/src/components/WorkspaceHome/index.jspackages/bruno-app/src/components/Sidebar/index.jspackages/bruno-app/src/components/Sidebar/StyledWrapper.jspackages/bruno-app/src/pages/Bruno/index.jspackages/bruno-app/src/providers/App/index.jspackages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.jspackages/bruno-electron/src/index.jspackages/bruno-app/src/components/Sidebar/Collections/CollectionSearch/index.jspackages/bruno-app/src/components/AppTitleBar/index.jspackages/bruno-app/src/components/Sidebar/Collections/index.jspackages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.jstests/collection/draft/draft-indicator.spec.tspackages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.jspackages/bruno-app/src/components/Icons/IconBottombarToggle/index.js
**/*.{test,spec}.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CODING_STANDARDS.md)
Add tests for any new functionality or meaningful changes; update corresponding tests when code is added, removed, or significantly modified
Files:
tests/collection/draft/draft-indicator.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/draft/draft-indicator.spec.ts
🧠 Learnings (2)
📚 Learning: 2025-12-03T08:09:57.124Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-03T08:09:57.124Z
Learning: Applies to **/*.{jsx,tsx} : Styled Components should be used as wrappers to define both self and children component styles; Tailwind classes should be used specifically for layout-based styles
Applied to files:
packages/bruno-app/src/components/Sidebar/Collections/CollectionSearch/StyledWrapper.jspackages/bruno-app/src/components/AppTitleBar/StyledWrapper.jspackages/bruno-app/src/components/Sidebar/Collections/StyledWrapper.jspackages/bruno-app/src/components/Sidebar/StyledWrapper.jspackages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js
📚 Learning: 2025-12-03T08:09:57.124Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-03T08:09:57.124Z
Learning: Applies to **/*.{jsx,tsx} : Styled Component CSS may change layout, but Tailwind classes should not define colors
Applied to files:
packages/bruno-app/src/components/Sidebar/Collections/CollectionSearch/StyledWrapper.jspackages/bruno-app/src/components/AppTitleBar/StyledWrapper.jspackages/bruno-app/src/components/Sidebar/Collections/StyledWrapper.jspackages/bruno-app/src/components/Sidebar/StyledWrapper.jspackages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js
🧬 Code graph analysis (8)
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (10)
packages/bruno-app/src/components/AppTitleBar/index.js (3)
dispatch(20-20)useSelector(43-43)activeWorkspace(47-47)packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js (1)
dispatch(12-12)packages/bruno-app/src/components/Sidebar/index.js (2)
dispatch(20-20)activeView(18-18)packages/bruno-app/src/components/WorkspaceHome/index.js (3)
dispatch(17-17)useSelector(18-18)activeWorkspace(33-33)packages/bruno-app/src/providers/App/index.js (1)
dispatch(16-16)packages/bruno-app/src/components/Sidebar/SidebarHeader/CloseWorkspace/index.js (2)
dispatch(9-9)useSelector(10-10)packages/bruno-app/src/components/Sidebar/Collections/index.js (3)
useSelector(12-12)useSelector(13-13)activeWorkspace(16-16)packages/bruno-app/src/components/Sidebar/CreateCollection/index.js (1)
CreateCollection(22-341)packages/bruno-app/src/components/Dropdown/index.js (1)
Dropdown(5-25)packages/bruno-app/src/components/Sidebar/Collections/RemoveCollectionsModal/index.js (1)
RemoveCollectionsModal(48-271)
packages/bruno-app/src/components/Sidebar/Collections/CollectionSearch/StyledWrapper.js (1)
packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js (1)
StyledWrapper(3-113)
packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js (3)
packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js (1)
Wrapper(3-113)packages/bruno-app/src/components/Sidebar/Collections/StyledWrapper.js (1)
Wrapper(3-34)packages/bruno-app/src/components/Sidebar/StyledWrapper.js (1)
Wrapper(3-65)
packages/bruno-app/src/pages/Bruno/index.js (1)
packages/bruno-app/src/components/AppTitleBar/index.js (2)
AppTitleBar(19-245)isConsoleOpen(46-46)
packages/bruno-electron/src/index.js (1)
packages/bruno-electron/src/utils/arch.js (1)
isMac(2-2)
packages/bruno-app/src/components/Sidebar/Collections/CollectionSearch/index.js (2)
packages/bruno-app/src/components/Sidebar/Collections/CollectionSearch/StyledWrapper.js (1)
StyledWrapper(3-63)packages/bruno-app/src/components/Sidebar/Collections/index.js (1)
searchText(11-11)
packages/bruno-app/src/components/AppTitleBar/index.js (3)
packages/bruno-app/src/providers/ReduxStore/slices/app.js (2)
savePreferences(132-142)savePreferences(132-142)packages/bruno-app/src/components/Dropdown/index.js (1)
Dropdown(5-25)packages/bruno-app/src/utils/workspaces/index.js (1)
isPinned(51-51)
packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js (1)
packages/bruno-app/src/components/Sidebar/Collections/CollectionSearch/StyledWrapper.js (1)
StyledWrapper(3-63)
⏰ 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: Playwright E2E Tests
- GitHub Check: Unit Tests
- GitHub Check: CLI Tests
- GitHub Check: SSL Tests - Windows
- GitHub Check: SSL Tests - macOS
- GitHub Check: SSL Tests - Linux
🔇 Additional comments (23)
packages/bruno-app/src/components/Sidebar/StyledWrapper.js (1)
57-64: LGTM! Clean placeholder implementation.The flexbox centering and theme-based muted color are appropriate for a tab placeholder. The implementation follows the established pattern in this file and adheres to the coding guidelines.
packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js (1)
376-378: LGTM!Adding
data-testidattribute enables stable test automation selectors. This aligns with the test update indraft-indicator.spec.ts.packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js (1)
8-8: LGTM!Reduced left padding tightens the collection name layout. Straightforward styling adjustment.
packages/bruno-app/src/components/WorkspaceHome/index.js (1)
9-9: Verify CloseWorkspace export from new location.The
CloseWorkspacecomponent import path has been updated tocomponents/Sidebar/SidebarHeader/CloseWorkspace. Confirm that:
- The component file exists at the new location
- The component is properly exported from
SidebarHeader- No other files still reference the old import path from
TitleBarpackages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js (1)
3-3: LGTM—Icon swap for visual consistency.The change from
IconFilestoIconBoxaligns with the broader UI refresh. The icon semantics (box/container) may better represent a collection.Also applies to: 48-48
packages/bruno-app/src/components/Sidebar/Collections/index.js (2)
18-23: LGTM—Memoized filtering improves performance.The memoized workspace collections filter is clean and correctly memoized with appropriate dependencies.
45-45: Verify layout behavior with static className.The className change from dynamic (dependent on
showSearch) to static may affect layout. Ensure search visibility transitions don't cause layout shifts by testing the sidebar behavior when toggling search visibility.packages/bruno-app/src/components/Icons/IconBottombarToggle/index.js (1)
3-14: LGTM—Clean icon component with conditional rendering.The component structure and conditional rect rendering for the collapsed state are well-implemented.
packages/bruno-app/src/pages/Bruno/index.js (2)
8-8: LGTM—AppTitleBar integrated cleanly.The new AppTitleBar component is properly imported and rendered.
Also applies to: 91-91
110-110: Simplify redundant conditional and verify AppTitleBar height.The height calculation has a redundant nested ternary: when
isConsoleOpenis true, the innerisConsoleOpen ? '300px' : '0px'will always return'300px'. Also, verify that 60px matches the actual AppTitleBar height.Apply this diff to simplify:
- height: isConsoleOpen ? `calc(100vh - 60px - ${isConsoleOpen ? '300px' : '0px'})` : 'calc(100vh - 60px)' + height: isConsoleOpen ? 'calc(100vh - 60px - 300px)' : 'calc(100vh - 60px)'packages/bruno-app/src/providers/App/index.js (1)
23-41: LGTM—Cleaner platform detection logic.The refactor improves readability with
includes(), upfronttoLowerCase(), and early returns. The empty string guard is good defensive coding.packages/bruno-app/src/components/Sidebar/index.js (2)
1-1: LGTM—SidebarHeader integration and view management.The replacement of TitleBar with SidebarHeader and new view switching logic is clean. The placeholder for future tab content is appropriate.
Also applies to: 17-18, 85-99
9-9: Verify minimum sidebar width increase doesn't conflict with responsive design.The
MIN_LEFT_SIDEBAR_WIDTHincreased from 221px to 250px (line 9). Ensure this change doesn't break layouts on smaller screens or conflict with existing responsive breakpoints. Check how this constant is used throughout the application and whether responsive constraints accommodate the new 250px minimum.packages/bruno-app/src/components/Sidebar/Collections/StyledWrapper.js (1)
3-28: LGTM—Clean flex layout with proper theme usage.The flex layout and scrollbar styling follow the styled-components pattern correctly, with appropriate theme token usage.
Based on learnings, Styled Components correctly define layout and use theme tokens for colors.
packages/bruno-app/src/components/Sidebar/Collections/CollectionSearch/StyledWrapper.js (1)
1-65: LGTM—Comprehensive search input styling with proper theming.The styled wrapper follows best practices with consistent theme token usage, smooth transitions, and well-defined interaction states.
Based on learnings, Styled Components correctly handle all styling including colors and layout.
packages/bruno-electron/src/index.js (2)
123-127: LGTM! OS-specific title bar configuration looks correct.The conditional logic for
titleBarStyle,titleBarOverlay, andtrafficLightPositionproperly handles macOS, Windows, and Linux scenarios. The 36px overlay height aligns with theAppTitleBarheight defined inStyledWrapper.js.
175-182: LGTM! Full-screen IPC events properly wired.These handlers correctly notify the renderer process about full-screen state changes, allowing the
AppTitleBarto adjust padding (e.g., removing macOS traffic light space in fullscreen).packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js (1)
1-248: LGTM! Well-structured cross-platform title bar styling.Good coverage for:
- macOS traffic light spacing (line 19) and fullscreen adjustment (lines 24-26)
- Windows caption button reservation (lines 241-245)
- Proper drag regions with
no-dragexceptions for interactive elementsThe fallback chain at line 203 (
props.theme.workspace?.accent || props.theme.colors?.text?.yellow) is a nice defensive pattern. Based on learnings, this correctly uses Styled Components for styling without Tailwind color definitions.packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (3)
35-50: LGTM! Component setup is clean.Good integration with Redux for workspace and collection state. The modal state management pattern with separate boolean flags is clear and maintainable.
52-77: LGTM! Import flow handles workspace types correctly.The branching logic for default vs. non-default workspaces (lines 55-63) correctly routes to different import flows. Error handling with toast notifications is appropriate.
180-291: LGTM! Dropdown menus well-structured.Good use of Tippy refs for programmatic dropdown control. The sort icon/label helpers (lines 255-259) provide clear visual feedback for the current sort state.
packages/bruno-app/src/components/AppTitleBar/index.js (2)
19-247: Overall structure looks good.The component is well-organized with clear separation of concerns between state management, event handlers, and rendering. Once the IPC cleanup issue is fixed and the
WorkspaceNamecomponent recreation is addressed, this will be solid.
88-96: Success toast may fire even when user cancels the dialog.If
openWorkspaceDialogresolves when the user cancels (without actually opening a workspace), the success toast would be misleading. Verify the action rejects or returns a distinguishable result on cancel.
| useEffect(() => { | ||
| const { ipcRenderer } = window; | ||
| if (!ipcRenderer) return; | ||
|
|
||
| const handleEnterFullScreen = ipcRenderer.on('main:enter-full-screen', () => { | ||
| setIsFullScreen(true); | ||
| }); | ||
|
|
||
| const handleLeaveFullScreen = ipcRenderer.on('main:leave-full-screen', () => { | ||
| setIsFullScreen(false); | ||
| }); | ||
|
|
||
| return () => { | ||
| if (handleEnterFullScreen) handleEnterFullScreen(); | ||
| if (handleLeaveFullScreen) handleLeaveFullScreen(); | ||
| }; | ||
| }, []); |
There was a problem hiding this comment.
IPC listener cleanup is incorrect and will cause memory leaks.
ipcRenderer.on() returns the ipcRenderer instance, not an unsubscribe function. The current cleanup does nothing, leading to accumulated listeners on component remount.
useEffect(() => {
const { ipcRenderer } = window;
if (!ipcRenderer) return;
- const handleEnterFullScreen = ipcRenderer.on('main:enter-full-screen', () => {
+ const onEnterFullScreen = () => {
setIsFullScreen(true);
- });
+ };
- const handleLeaveFullScreen = ipcRenderer.on('main:leave-full-screen', () => {
+ const onLeaveFullScreen = () => {
setIsFullScreen(false);
- });
+ };
+
+ ipcRenderer.on('main:enter-full-screen', onEnterFullScreen);
+ ipcRenderer.on('main:leave-full-screen', onLeaveFullScreen);
return () => {
- if (handleEnterFullScreen) handleEnterFullScreen();
- if (handleLeaveFullScreen) handleLeaveFullScreen();
+ ipcRenderer.removeListener('main:enter-full-screen', onEnterFullScreen);
+ ipcRenderer.removeListener('main:leave-full-screen', onLeaveFullScreen);
};
}, []);🤖 Prompt for AI Agents
In packages/bruno-app/src/components/AppTitleBar/index.js around lines 24 to 40
the code assigns the return value of ipcRenderer.on() to variables and calls
them in cleanup, but ipcRenderer.on() returns the ipcRenderer object (not an
unsubscribe), so listeners are never removed; fix by defining named handler
functions (e.g. const onEnter = () => setIsFullScreen(true); const onLeave = ()
=> setIsFullScreen(false)), register them with
ipcRenderer.on('main:enter-full-screen', onEnter) and
ipcRenderer.on('main:leave-full-screen', onLeave), and in the cleanup call
ipcRenderer.removeListener('main:enter-full-screen', onEnter) and
ipcRenderer.removeListener('main:leave-full-screen', onLeave) (or
ipcRenderer.off if available), keeping the existing ipcRenderer existence guard.
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js
Outdated
Show resolved
Hide resolved
packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js
Outdated
Show resolved
Hide resolved
packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js
Show resolved
Hide resolved
…up SidebarHeader styles
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
packages/bruno-app/src/components/AppTitleBar/index.js (1)
24-40: Fix fullscreen IPC listener cleanup – current code leaks listeners.
ipcRenderer.on()returns the ipcRenderer instance, not an unsubscribe function, soremoveEnterFullScreenListener()/removeLeaveFullScreenListener()in cleanup are no-ops and handlers will accumulate on remount.Suggested fix:
useEffect(() => { const { ipcRenderer } = window; if (!ipcRenderer) return; - const removeEnterFullScreenListener = ipcRenderer.on('main:enter-full-screen', () => { - setIsFullScreen(true); - }); - - const removeLeaveFullScreenListener = ipcRenderer.on('main:leave-full-screen', () => { - setIsFullScreen(false); - }); - - return () => { - removeEnterFullScreenListener(); - removeLeaveFullScreenListener(); - }; + const onEnterFullScreen = () => { + setIsFullScreen(true); + }; + + const onLeaveFullScreen = () => { + setIsFullScreen(false); + }; + + ipcRenderer.on('main:enter-full-screen', onEnterFullScreen); + ipcRenderer.on('main:leave-full-screen', onLeaveFullScreen); + + return () => { + ipcRenderer.removeListener('main:enter-full-screen', onEnterFullScreen); + ipcRenderer.removeListener('main:leave-full-screen', onLeaveFullScreen); + }; }, []);
🧹 Nitpick comments (5)
packages/bruno-app/src/components/AppTitleBar/index.js (3)
60-67: TightentoTitleCasesplit regex to avoid ambiguous-range.
str.split(/[\s-_]+/)uses-inside a character class in a slightly ambiguous way; more explicit is safer and clearer.- return str - .split(/[\s-_]+/) + return str + .split(/[\s_-]+/) .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(' ');
69-76: Use functional state update when toggling dropdown visibility.Using
setShowWorkspaceDropdown(!showWorkspaceDropdown)can suffer from stale closures under rapid updates; the functional form is more robust.- <div ref={ref} className="workspace-name-container" onClick={() => setShowWorkspaceDropdown(!showWorkspaceDropdown)}> + <div + ref={ref} + className="workspace-name-container" + onClick={() => setShowWorkspaceDropdown((prev) => !prev)} + >
88-96: VerifyopenWorkspaceDialogasync behavior and success/error toasts.Right now you always
await dispatch(openWorkspaceDialog())and unconditionally show a success toast unless an exception is thrown. In many Redux setups (e.g. RTK thunks),dispatch(thunk)resolves even on failure unless you explicitly unwrap or inspectmeta, so errors/cancels might still show “Workspace opened successfully”.Consider something along these lines (depending on how
openWorkspaceDialogis implemented):const resultAction = await dispatch(openWorkspaceDialog()); if (openWorkspaceDialog.fulfilled.match(resultAction)) { toast.success('Workspace opened successfully'); } else if (openWorkspaceDialog.rejected.match(resultAction)) { const message = resultAction.error?.message || 'Failed to open workspace'; toast.error(message); }Please double‑check the actual action shape and adjust accordingly.
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (2)
39-45: Combine collection selectors into a singleuseSelectorcall.You can destructure
collectionsandcollectionSortOrderin oneuseSelectorto avoid duplicate subscriptions and keep the state derivation in one place.- // Get collection sort order - const { collections } = useSelector((state) => state.collections); - const { collectionSortOrder } = useSelector((state) => state.collections); + // Collections list and current sort order + const { collections, collectionSortOrder } = useSelector((state) => state.collections);
35-347: Consider adding JSDoc forSidebarHeaderprops and behavior.Given this component coordinates view switching, search visibility, modals, and Electron devtools, a brief JSDoc on
SidebarHeaderdocumentingsetShowSearch,activeView, andonViewChangewould help future maintainers.+/** + * Sidebar header for the left panel: renders the current view label/tabs, + * collections actions (search, add/import/open, sort, close all, devtools), + * and manages the related modals. + * + * @param {Object} props + * @param {(updater: (prev: boolean) => boolean) => void} [props.setShowSearch] Toggle for the sidebar search bar. + * @param {string} [props.activeView='collections'] Currently active sidebar view id. + * @param {(viewId: string) => void} [props.onViewChange] Callback when the active view tab changes. + */ const SidebarHeader = ({ setShowSearch, activeView = 'collections', onViewChange }) => {As per coding guidelines, this brings the abstraction in line with our JSDoc expectations.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
packages/bruno-app/src/components/AppTitleBar/index.js(1 hunks)packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js(1 hunks)packages/bruno-app/src/components/Sidebar/SidebarHeader/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, never tabs
Use single quotes for strings instead of double quotes
Always add semicolons at the end of statements
No trailing commas in code
Always use parentheses around parameters in arrow functions, even for single parameters
For multiline constructs, put opening braces on the same line with a minimum of 2 elements for multiline formatting
No newlines inside function parentheses
Space before and after the arrow in arrow functions:() => {}
No space between function name and parentheses:func()notfunc ()
Semicolons should go at the end of the line, not on a new line
Function names should be concise and descriptive
Add JSDoc comments to provide details to abstractions
Avoid single-line abstractions where all that is being done is increasing the call stack with one additional function
Add meaningful comments to explain complex code flow instead of obvious comments
Files:
packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.jspackages/bruno-app/src/components/Sidebar/SidebarHeader/index.jspackages/bruno-app/src/components/AppTitleBar/index.js
🧠 Learnings (4)
📚 Learning: 2025-12-03T08:09:57.124Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-03T08:09:57.124Z
Learning: Applies to **/*.{jsx,tsx} : Styled Components should be used as wrappers to define both self and children component styles; Tailwind classes should be used specifically for layout-based styles
Applied to files:
packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js
📚 Learning: 2025-12-03T08:09:57.124Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-03T08:09:57.124Z
Learning: Applies to **/*.{jsx,tsx} : Styled Component CSS may change layout, but Tailwind classes should not define colors
Applied to files:
packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js
📚 Learning: 2025-12-03T08:09:57.123Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-03T08:09:57.123Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Space before and after the arrow in arrow functions: `() => {}`
Applied to files:
packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js
📚 Learning: 2025-12-03T08:09:57.123Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-03T08:09:57.123Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : For multiline constructs, put opening braces on the same line with a minimum of 2 elements for multiline formatting
Applied to files:
packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js
🧬 Code graph analysis (2)
packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js (1)
packages/bruno-app/src/components/Sidebar/Collections/CollectionSearch/StyledWrapper.js (1)
StyledWrapper(3-63)
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (8)
packages/bruno-app/src/components/Sidebar/SidebarHeader/WorkspaceSelector/index.js (3)
dispatch(14-14)useSelector(16-16)activeWorkspace(18-18)packages/bruno-app/src/components/Sidebar/index.js (2)
dispatch(20-20)activeView(18-18)packages/bruno-app/src/providers/App/index.js (1)
dispatch(16-16)packages/bruno-app/src/components/Sidebar/Collections/index.js (3)
useSelector(12-12)useSelector(13-13)activeWorkspace(16-16)packages/bruno-app/src/components/Sidebar/CreateCollection/index.js (1)
CreateCollection(22-341)packages/bruno-app/src/components/Sidebar/ImportCollection/index.js (1)
ImportCollection(36-170)packages/bruno-app/src/components/Sidebar/ImportCollectionLocation/index.js (1)
ImportCollectionLocation(84-231)packages/bruno-js/src/sandbox/quickjs/index.js (1)
e(79-79)
⏰ 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: CLI Tests
- GitHub Check: Playwright E2E Tests
- GitHub Check: Unit Tests
- GitHub Check: SSL Tests - macOS
- GitHub Check: SSL Tests - Windows
- GitHub Check: SSL Tests - Linux
🔇 Additional comments (3)
packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js (1)
3-108: SidebarHeader StyledWrapper CSS and theme usage look correct.The selector structure is clean (no stray braces/props), and the layout + color usage is consistently driven by
theme.sidebar/theme.dropdowntokens, including sane fallbacks on hover. This should integrate well with existing sidebar theming and avoids the CSS parsing issues noted in earlier reviews.Based on learnings, this matches our styled‑components vs. Tailwind usage guidelines.
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (2)
178-290: Collections actions and dropdown flows are cohesive and match existing patterns.The search toggle, add/import/open flows, sort/close-all, and devtools actions are wired cleanly with tippy refs and toasts, and the
collectionsToClosestate plusRemoveCollectionsModalhook-in keeps the close-all flow localized to the header.
37-38: GuardipcRendererusage so Devtools action is safe outside Electron.If this component renders where
window.ipcRendereris missing or stubbed,ipcRenderer.invokewill throw. Add optional chaining to protect against undefinedipcRenderer:- ipcRenderer.invoke('renderer:open-devtools'); + if (ipcRenderer?.invoke) { + ipcRenderer.invoke('renderer:open-devtools'); + }This applies to lines 37-38 and 148-150. Verify your preload exposes
ipcRendererconditionally and check if other call sites use a similar defensive pattern.
…default order in SidebarHeader
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
packages/bruno-app/src/components/AppTitleBar/index.js (1)
23-40: FixipcRenderer.onlistener cleanup to prevent memory leaks
ipcRenderer.on()returns theipcRendererinstance, not an unsubscribe function, so callingremoveEnterFullScreenListener()/removeLeaveFullScreenListener()in the cleanup does nothing and listeners will accumulate on remount.useEffect(() => { const { ipcRenderer } = window; if (!ipcRenderer) return; - const removeEnterFullScreenListener = ipcRenderer.on('main:enter-full-screen', () => { - setIsFullScreen(true); - }); - - const removeLeaveFullScreenListener = ipcRenderer.on('main:leave-full-screen', () => { - setIsFullScreen(false); - }); - - return () => { - removeEnterFullScreenListener(); - removeLeaveFullScreenListener(); - }; + const handleEnterFullScreen = () => { + setIsFullScreen(true); + }; + + const handleLeaveFullScreen = () => { + setIsFullScreen(false); + }; + + ipcRenderer.on('main:enter-full-screen', handleEnterFullScreen); + ipcRenderer.on('main:leave-full-screen', handleLeaveFullScreen); + + return () => { + ipcRenderer.removeListener('main:enter-full-screen', handleEnterFullScreen); + ipcRenderer.removeListener('main:leave-full-screen', handleLeaveFullScreen); + }; }, []);
🧹 Nitpick comments (2)
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (1)
39-45: Merge collections selectors into a singleuseSelectorcallYou’re selecting from
state.collectionstwice; combining them avoids duplicate subscriptions and keeps the slice snapshot consistent.- const { collections } = useSelector((state) => state.collections); - const { collectionSortOrder } = useSelector((state) => state.collections); + const { collections, collectionSortOrder } = useSelector((state) => state.collections);packages/bruno-app/src/components/AppTitleBar/index.js (1)
59-67: Make thetoTitleCasesplit regex explicit
/[\s-_]+/is a bit ambiguous because of the-inside the character class. Using/[\s_-]+/makes it clear you’re splitting on whitespace, hyphen, or underscore.- return str - .split(/[\s-_]+/) + return str + .split(/[\s_-]+/) .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(' ');
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
packages/bruno-app/src/components/AppTitleBar/index.js(1 hunks)packages/bruno-app/src/components/Sidebar/SidebarHeader/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, never tabs
Use single quotes for strings instead of double quotes
Always add semicolons at the end of statements
No trailing commas in code
Always use parentheses around parameters in arrow functions, even for single parameters
For multiline constructs, put opening braces on the same line with a minimum of 2 elements for multiline formatting
No newlines inside function parentheses
Space before and after the arrow in arrow functions:() => {}
No space between function name and parentheses:func()notfunc ()
Semicolons should go at the end of the line, not on a new line
Function names should be concise and descriptive
Add JSDoc comments to provide details to abstractions
Avoid single-line abstractions where all that is being done is increasing the call stack with one additional function
Add meaningful comments to explain complex code flow instead of obvious comments
Files:
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.jspackages/bruno-app/src/components/AppTitleBar/index.js
🧬 Code graph analysis (2)
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (4)
packages/bruno-app/src/components/Sidebar/ImportCollection/index.js (1)
ImportCollection(36-170)packages/bruno-app/src/components/Sidebar/ImportCollectionLocation/index.js (1)
ImportCollectionLocation(84-231)packages/bruno-app/src/components/Dropdown/index.js (1)
Dropdown(5-25)packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js (1)
StyledWrapper(3-108)
packages/bruno-app/src/components/AppTitleBar/index.js (3)
packages/bruno-app/src/providers/ReduxStore/slices/app.js (2)
savePreferences(132-142)savePreferences(132-142)packages/bruno-app/src/components/Dropdown/index.js (1)
Dropdown(5-25)packages/bruno-app/src/utils/workspaces/index.js (1)
isPinned(51-51)
⏰ 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 - macOS
- GitHub Check: CLI Tests
- GitHub Check: Playwright E2E Tests
- GitHub Check: Unit Tests
- GitHub Check: SSL Tests - Linux
🔇 Additional comments (1)
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (1)
151-153: GuardipcRendererinopenDevToolsto avoid runtime errorsIf this component is shared between Electron and web builds, unguarded
ipcRendereraccess will throw at runtime. Add a defensive check before invoking.- const openDevTools = () => { - ipcRenderer.invoke('renderer:open-devtools'); - }; + const openDevTools = () => { + if (!ipcRenderer?.invoke) { + return; + } + + ipcRenderer.invoke('renderer:open-devtools'); + };
| }} | ||
| > | ||
| <IconDeviceDesktop size={16} stroke={1.5} className="icon" /> | ||
| Devtools |
There was a problem hiding this comment.
Rename to Dev Utils. Update functionality accordingly.
| <TitleBar /> | ||
| <Collections /> | ||
| <div className="flex flex-col flex-grow" style={{ minHeight: 0, overflow: 'hidden' }}> | ||
| <SidebarHeader |
There was a problem hiding this comment.
Delete CollectionHeader component if not in use anymore.
| const onWorkspaceDropdownCreate = (ref) => (workspaceDropdownTippyRef.current = ref); | ||
|
|
||
| // Helper function to convert workspace names to title case | ||
| const toTitleCase = (str) => { |
…ntations in AppTitleBar and WorkspaceSelector
…le buttons in AppTitleBar
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
packages/bruno-app/src/components/AppTitleBar/index.js (1)
25-41: Fix Electron IPC listener cleanup; current code leaks listeners and will throw on unmount.
ipcRenderer.on()returns theipcRendererinstance, not an unsubscribe function. Calling the returned value as a function in cleanup both fails to remove listeners and is likely to throw. Use named handlers andremoveListenerinstead.- useEffect(() => { - const { ipcRenderer } = window; - if (!ipcRenderer) return; - - const removeEnterFullScreenListener = ipcRenderer.on('main:enter-full-screen', () => { - setIsFullScreen(true); - }); - - const removeLeaveFullScreenListener = ipcRenderer.on('main:leave-full-screen', () => { - setIsFullScreen(false); - }); - - return () => { - removeEnterFullScreenListener(); - removeLeaveFullScreenListener(); - }; - }, []); + useEffect(() => { + const { ipcRenderer } = window; + if (!ipcRenderer) return; + + const onEnterFullScreen = () => { + setIsFullScreen(true); + }; + + const onLeaveFullScreen = () => { + setIsFullScreen(false); + }; + + ipcRenderer.on('main:enter-full-screen', onEnterFullScreen); + ipcRenderer.on('main:leave-full-screen', onLeaveFullScreen); + + return () => { + ipcRenderer.removeListener('main:enter-full-screen', onEnterFullScreen); + ipcRenderer.removeListener('main:leave-full-screen', onLeaveFullScreen); + }; + }, []);
🧹 Nitpick comments (6)
tests/footer/sidebar-toggle/sidebar-toggle.spec.js (2)
7-7: Prefer accessibility-based selectors over testId.Changing from
getByLabel('Toggle Sidebar')togetByTestId('toggle-sidebar-button')moves away from user-facing and accessibility-based testing. Playwright recommendsgetByRole,getByLabel, orgetByTextovergetByTestIdas they verify proper accessible labeling and test how users interact with the UI.Consider reverting to a semantic selector if the button has proper accessible labeling:
- const toggleButton = page.getByTestId('toggle-sidebar-button'); + const toggleButton = page.getByRole('button', { name: 'Toggle Sidebar' });Or verify the button's accessibility attributes in the implementation to confirm which selector is appropriate.
3-36: Optional: Consider using test.step for better reporting.While not required for this straightforward test, wrapping logical sections in
test.stepwould improve readability in generated reports, as recommended in the coding guidelines.Example refactor:
test('should toggle sidebar visibility when clicking the toggle button', async ({ page }) => { await test.step('Verify initial sidebar visibility', async () => { const sidebar = page.locator('aside.sidebar'); const dragHandle = page.locator('.sidebar-drag-handle'); await expect(sidebar).toBeVisible(); await expect(dragHandle).toBeVisible(); }); await test.step('Hide sidebar', async () => { const toggleButton = page.getByTestId('toggle-sidebar-button'); const sidebar = page.locator('aside.sidebar'); const dragHandle = page.locator('.sidebar-drag-handle'); await toggleButton.click(); await expect(sidebar).not.toBeVisible(); await expect(dragHandle).not.toBeVisible(); const sidebarBox = await sidebar.boundingBox(); expect(sidebarBox?.width).toBe(0); }); await test.step('Show sidebar again', async () => { const toggleButton = page.getByTestId('toggle-sidebar-button'); const sidebar = page.locator('aside.sidebar'); const dragHandle = page.locator('.sidebar-drag-handle'); await toggleButton.click(); await expect(sidebar).toBeVisible(); await expect(dragHandle).toBeVisible(); const expandedSidebarBox = await sidebar.boundingBox(); expect(expandedSidebarBox?.width).toBeGreaterThan(0); }); });packages/bruno-app/src/components/AppTitleBar/index.js (4)
55-59: Drop unused Tippy ref for workspace dropdown unless you actually need it.
workspaceDropdownTippyRefandonWorkspaceDropdownCreateare assigned but never read, so they add noise without behavior. Either wire them up or remove them (and theonCreateprop).const [createWorkspaceModalOpen, setCreateWorkspaceModalOpen] = useState(false); const [showWorkspaceDropdown, setShowWorkspaceDropdown] = useState(false); - const workspaceDropdownTippyRef = useRef(); - const onWorkspaceDropdownCreate = (ref) => (workspaceDropdownTippyRef.current = ref);And in the dropdown JSX:
- <Dropdown - onCreate={onWorkspaceDropdownCreate} - icon={<WorkspaceName />} + <Dropdown + icon={<WorkspaceName />} placement="bottom-start" style="new" visible={showWorkspaceDropdown} onClickOutside={() => setShowWorkspaceDropdown(false)} >
73-77: Guard the workspace switch toast against missing names and align casing with the UI.If
workspaces.finddoesn’t find a match, the toast will showundefined, and the casing is currently raw. You already havetoTitleCase; using it here keeps things consistent and safer.const handleWorkspaceSwitch = (workspaceUid) => { dispatch(switchWorkspace(workspaceUid)); setShowWorkspaceDropdown(false); - toast.success(`Switched to ${workspaces.find((w) => w.uid === workspaceUid)?.name}`); + const workspace = workspaces.find((w) => w.uid === workspaceUid); + toast.success(`Switched to ${toTitleCase(workspace?.name) || 'workspace'}`); };
101-125: Clarify layout toggle preferences update and defensively handle undefinedpreferences.Spreading
preferencesassumes it’s always non-null, and...preferences?.layout || {}relies on operator precedence. A small refactor makes the intent clearer and safer ifpreferencesstarts out undefined.- const orientation = preferences?.layout?.responsePaneOrientation || 'horizontal'; - - const handleToggleVerticalLayout = () => { - const newOrientation = orientation === 'horizontal' ? 'vertical' : 'horizontal'; - const updatedPreferences = { - ...preferences, - layout: { - ...preferences?.layout || {}, - responsePaneOrientation: newOrientation - } - }; - dispatch(savePreferences(updatedPreferences)); - }; + const orientation = preferences?.layout?.responsePaneOrientation || 'horizontal'; + + const handleToggleVerticalLayout = () => { + const newOrientation = orientation === 'horizontal' ? 'vertical' : 'horizontal'; + const layout = preferences?.layout || {}; + const updatedPreferences = { + ...(preferences || {}), + layout: { + ...layout, + responsePaneOrientation: newOrientation + } + }; + dispatch(savePreferences(updatedPreferences)); + };
20-20: Add a brief JSDoc comment forAppTitleBarto document its role.Given this is a central abstraction in the new layout, a short JSDoc will help future readers understand its responsibilities.
-import { toTitleCase } from 'utils/common/index'; - -const AppTitleBar = () => { +import { toTitleCase } from 'utils/common/index'; + +/** + * Application-level title bar hosting the workspace selector, branding, and global layout toggles. + */ +const AppTitleBar = () => {
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
packages/bruno-app/src/components/AppTitleBar/index.js(1 hunks)packages/bruno-app/src/components/StatusBar/index.js(1 hunks)tests/footer/sidebar-toggle/sidebar-toggle.spec.js(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CODING_STANDARDS.md)
**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation, never tabs
Use single quotes for strings instead of double quotes
Always add semicolons at the end of statements
No trailing commas in code
Always use parentheses around parameters in arrow functions, even for single parameters
For multiline constructs, put opening braces on the same line with a minimum of 2 elements for multiline formatting
No newlines inside function parentheses
Space before and after the arrow in arrow functions:() => {}
No space between function name and parentheses:func()notfunc ()
Semicolons should go at the end of the line, not on a new line
Function names should be concise and descriptive
Add JSDoc comments to provide details to abstractions
Avoid single-line abstractions where all that is being done is increasing the call stack with one additional function
Add meaningful comments to explain complex code flow instead of obvious comments
Files:
tests/footer/sidebar-toggle/sidebar-toggle.spec.jspackages/bruno-app/src/components/StatusBar/index.jspackages/bruno-app/src/components/AppTitleBar/index.js
**/*.{test,spec}.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CODING_STANDARDS.md)
Add tests for any new functionality or meaningful changes; update corresponding tests when code is added, removed, or significantly modified
Files:
tests/footer/sidebar-toggle/sidebar-toggle.spec.js
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/footer/sidebar-toggle/sidebar-toggle.spec.js
🧠 Learnings (1)
📚 Learning: 2025-12-03T08:09:57.124Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-03T08:09:57.124Z
Learning: Applies to **/*.{test,spec}.{js,jsx,ts,tsx} : Add tests for any new functionality or meaningful changes; update corresponding tests when code is added, removed, or significantly modified
Applied to files:
tests/footer/sidebar-toggle/sidebar-toggle.spec.js
🧬 Code graph analysis (1)
packages/bruno-app/src/components/AppTitleBar/index.js (3)
packages/bruno-app/src/utils/common/index.js (2)
toTitleCase(353-360)toTitleCase(353-360)packages/bruno-app/src/providers/ReduxStore/slices/app.js (2)
savePreferences(132-142)savePreferences(132-142)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: Unit Tests
- GitHub Check: Playwright E2E Tests
- GitHub Check: CLI Tests
- GitHub Check: SSL Tests - Linux
- GitHub Check: SSL Tests - Windows
- GitHub Check: SSL Tests - macOS
🔇 Additional comments (1)
packages/bruno-app/src/components/StatusBar/index.js (1)
12-12: Import narrowing matches StatusBar’s reduced responsibilitiesUpdating this import so StatusBar only pulls in
showPreferences(and not sidebar toggle actions) is consistent with moving sidebar/workspace controls into the new titlebar/sidebar header. The remaining usages ofshowPreferencesin this component look correct.
| const WorkspaceName = forwardRef((props, ref) => { | ||
| return ( | ||
| <div ref={ref} className="workspace-name-container" onClick={() => setShowWorkspaceDropdown(!showWorkspaceDropdown)}> | ||
| <span className="workspace-name">{toTitleCase(activeWorkspace?.name) || 'Default Workspace'}</span> | ||
| <IconChevronDown size={14} stroke={1.5} className="chevron-icon" /> | ||
| </div> | ||
| ); | ||
| }); |
There was a problem hiding this comment.
Make the workspace dropdown trigger keyboard-accessible and use a proper button element.
Right now the trigger is a <div> with onClick, which is not focusable or activatable via keyboard and has no ARIA metadata. That’s a blocking accessibility issue for switching workspaces.
You can convert it to a real button with ARIA attributes and a functional state update:
- const WorkspaceName = forwardRef((props, ref) => {
- return (
- <div ref={ref} className="workspace-name-container" onClick={() => setShowWorkspaceDropdown(!showWorkspaceDropdown)}>
- <span className="workspace-name">{toTitleCase(activeWorkspace?.name) || 'Default Workspace'}</span>
- <IconChevronDown size={14} stroke={1.5} className="chevron-icon" />
- </div>
- );
- });
+ const WorkspaceName = forwardRef((props, ref) => {
+ return (
+ <button
+ ref={ref}
+ type="button"
+ className="workspace-name-container"
+ onClick={() => setShowWorkspaceDropdown((prev) => !prev)}
+ aria-haspopup="listbox"
+ aria-expanded={showWorkspaceDropdown}
+ >
+ <span className="workspace-name">{toTitleCase(activeWorkspace?.name) || 'Default Workspace'}</span>
+ <IconChevronDown size={14} stroke={1.5} className="chevron-icon" />
+ </button>
+ );
+ });No changes needed to the <Dropdown icon={<WorkspaceName />} ...> usage.
Also applies to: 140-148
🤖 Prompt for AI Agents
In packages/bruno-app/src/components/AppTitleBar/index.js around lines 60 to 67
(and similarly apply the same change to lines 140 to 148), replace the
non-focusable <div> trigger with a semantic <button type="button"> that forwards
the ref, keeps the same className and click handler, and adds ARIA attributes:
aria-haspopup="menu" and aria-expanded={showWorkspaceDropdown} so screen readers
know it toggles a menu; ensure you keep the onClick that calls
setShowWorkspaceDropdown(!showWorkspaceDropdown) and remove any onKey handlers
since a native button handles keyboard activation.
| <button | ||
| className="titlebar-action-button" | ||
| onClick={handleToggleSidebar} | ||
| title={sidebarCollapsed ? 'Show sidebar' : 'Hide sidebar'} |
There was a problem hiding this comment.
Can we use ToolHint instead of title?
| <IconChevronDown size={14} stroke={1.5} className="chevron-icon" /> | ||
| </div> | ||
| ); | ||
| }); |
There was a problem hiding this comment.
@abhishek-bruno Please address the above in a followup PR
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
tests/response/response-actions.spec.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CODING_STANDARDS.md)
**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation, never tabs
Use single quotes for strings instead of double quotes
Always add semicolons at the end of statements
No trailing commas in code
Always use parentheses around parameters in arrow functions, even for single parameters
For multiline constructs, put opening braces on the same line with a minimum of 2 elements for multiline formatting
No newlines inside function parentheses
Space before and after the arrow in arrow functions:() => {}
No space between function name and parentheses:func()notfunc ()
Semicolons should go at the end of the line, not on a new line
Function names should be concise and descriptive
Add JSDoc comments to provide details to abstractions
Avoid single-line abstractions where all that is being done is increasing the call stack with one additional function
Add meaningful comments to explain complex code flow instead of obvious comments
Files:
tests/response/response-actions.spec.ts
**/*.{test,spec}.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CODING_STANDARDS.md)
Add tests for any new functionality or meaningful changes; update corresponding tests when code is added, removed, or significantly modified
Files:
tests/response/response-actions.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/response/response-actions.spec.ts
🧠 Learnings (1)
📚 Learning: 2025-12-03T08:09:57.124Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-03T08:09:57.124Z
Learning: Applies to **/*.{test,spec}.{js,jsx,ts,tsx} : Add tests for any new functionality or meaningful changes; update corresponding tests when code is added, removed, or significantly modified
Applied to files:
tests/response/response-actions.spec.ts
⏰ 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: CLI Tests
- GitHub Check: Playwright E2E Tests
- GitHub Check: Unit Tests
- GitHub Check: SSL Tests - Windows
- GitHub Check: SSL Tests - Linux
- GitHub Check: SSL Tests - macOS
…no#6319) * refactor: update sidebar components and styles, replace TitleBar with SidebarHeader, and enhance collections search functionality * refactor: improve event listener management in AppTitleBar and clean up SidebarHeader styles * fix: ensure safe access to layout preferences in AppTitleBar and set default order in SidebarHeader * refactor: centralize toTitleCase utility and remove redundant implementations in AppTitleBar and WorkspaceSelector * feat: enhance accessibility and testing for sidebar and devtools toggle buttons in AppTitleBar * chore: quick fix on a flaky test --------- Co-authored-by: Bijin A B <bijin@usebruno.com>
Description
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
Refactor
Style
Tests
✏️ Tip: You can customize this high-level summary in your review settings.