Skip to content

Conversation

@helloanoop
Copy link
Contributor

@helloanoop helloanoop commented Dec 27, 2025

Custom Themes + UX Overhaul

Summary by CodeRabbit

Release Notes

  • New Features

    • Added comprehensive theme customization system with multiple theme variants (Catppuccin, Nord, VS Code, Monochrome, Pastel)
    • Added theme mode selector (Light, Dark, System) in preferences
    • Added theme variant selection UI for custom theme configuration
  • Style

    • Refactored color theming system to use centralized theme tokens instead of CSS variables
    • Updated UI component styling across the application for consistency
    • Improved visual appearance with updated color palettes
  • Chores

    • Added theme validation schema and dependencies for theming system

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

Copilot AI review requested due to automatic review settings December 27, 2025 03:38
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 27, 2025

Note

Other AI code review bot(s) detected

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

Walkthrough

Major theming system overhaul introducing variant-based theme selection (light/dark with multiple palette options), refactoring component styling to use centralized theme tokens, replacing native buttons with a shared Button component, restructuring GrpcBody and WsBody message UIs with dedicated toolbars, and removing legacy components.

Changes

Cohort / File(s) Summary
Theme System & Variants
packages/bruno-app/src/providers/Theme/index.js, packages/bruno-app/src/themes/index.js, packages/bruno-app/src/themes/schema/*
Introduced theme variant system storing themeVariantLight/Dark separately; added runtime validation via schema; expanded theme registry with new palette options (Catppuccin Latte/Frappé/Macchiato/Mocha, Nord, VS Code Light/Dark, Light/Dark Monochrome/Pastel).
New Theme Definitions
packages/bruno-app/src/themes/dark/*, packages/bruno-app/src/themes/light/*
Added comprehensive theme modules for 8 new theme variants; migrated base dark/light themes to new structure with accents, background, overlay nested tokens; applied color token updates across all new themes.
GrpcBody Refactoring
packages/bruno-app/src/components/RequestPane/GrpcBody/index.js, packages/bruno-app/src/components/RequestPane/GrpcBody/StyledWrapper.js
Introduced MessageToolbar component; replaced collapsible message headers with toolbar-based UI; restructured layout into messages-container/message-container/editor-container blocks; removed collapse state management.
WsBody & WebSocket Messages
packages/bruno-app/src/components/RequestPane/WsBody/index.js, packages/bruno-app/src/components/RequestPane/WsBody/StyledWrapper.js, packages/bruno-app/src/components/RequestPane/WsBody/SingleWSMessage/*
Simplified message rendering with messagesToShow filter; replaced collapsible UI with consistent toolbar layout; removed collapse state; added isLast prop for styling; updated container classes for single/multi modes.
CSS Variable → Theme Token Migration (Inactive Tabs)
packages/bruno-app/src/components/*/StyledWrapper.js (30+ files)
Replaced var(--color-tab-inactive) with props.theme.colors.text.subtext0 across CollectionSettings, FolderSettings, RequestPane components, ResponsePane components, and UI elements.
Brand Color Updates
packages/bruno-app/src/components/*/BodyMode/StyledWrapper.js, packages/bruno-app/src/components/BodyModeSelector/StyledWrapper.js, packages/bruno-app/src/components/ResponseExample/* (6+ files)
Changed .selected-body-mode color from theme.colors.text.yellow to theme.brand across request and response body mode indicators.
Dropdown & Active Item Styling
packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js, packages/bruno-app/src/components/Dropdown/StyledWrapper.js, packages/bruno-app/src/components/Environments/EnvironmentSelector/*
Removed .dropdown-item-active rules from AppTitleBar; updated Dropdown active item color to theme.dropdown.selectedColor with rgba transparency; changed EnvironmentListContent class from 'active' to 'dropdown-item-active'.
Border & Surface Theme Updates
packages/bruno-app/src/components/*/StyledWrapper.js, packages/bruno-app/src/components/EditableTable/StyledWrapper.js, packages/bruno-app/src/components/Table/StyledWrapper.js (10+ files)
Replaced theme.requestTabs.active.bg with theme.background.surface2; replaced indent borders with theme.border.border0; updated table cell borders across EditableTable and Table variants.
Text Color & Muted Token Updates
packages/bruno-app/src/components/*/StyledWrapper.js (15+ files)
Changed primary dropdown text from theme.dropdown.primaryText to theme.text; updated muted colors to theme.colors.text.muted; applied across ResponseExample, ManageWorkspace, and response headers.
Button Component Adoption
packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js, packages/bruno-app/src/components/WorkspaceHome/*, packages/bruno-app/src/ui/Button/StyledWrapper.js (5+ files)
Replaced native <button> elements with shared ui/Button component; adjusted button sizing and typography (font-size reductions for base/md, increase for lg); updated hover state border/background logic.
Modal & Tooltip Styling
packages/bruno-app/src/components/Modal/StyledWrapper.js, packages/bruno-app/src/components/ToolHint/*
Removed StyledWrapper from ToolHint; changed modal close-button hover to rgba-based transparency; removed font-weight from modal header.
Preferences & Theme Customization UI
packages/bruno-app/src/components/Preferences/Themes/*, packages/bruno-app/src/components/Preferences/index.js
Added new Themes component with ThemePreview and ThemeVariantCard; integrated into Preferences tab panel; added theme mode selector and variant cards for light/dark/system modes.
Status Bar Theme Dropdown
packages/bruno-app/src/components/StatusBar/ThemeDropdown/*, packages/bruno-app/src/components/StatusBar/index.js
Introduced ThemeDropdown component with Tippy popovers for theme mode selection and light/dark theme variant submenus; replaced sidebar toggle with palette icon.
GrpcResponsePane Refactoring
packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/GrpcQueryResult/*
Removed Accordion usage; implemented custom collapsible UI with expandedIndex state; replaced Accordion-driven rendering with manual message item list; removed timestamp formatting.
WebSocket Response List Styling
packages/bruno-app/src/components/ResponsePane/WsResponsePane/WSMessagesList/*
Consolidated message styling under .ws-message with nested modifiers; added message type-specific icon colors (incoming=green, outgoing=yellow, info=blue, error=danger); introduced empty-state styling.
Component Removals
packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecBadge/*, packages/bruno-app/src/components/Table-v2/*, packages/bruno-app/src/components/ToolHint/StyledWrapper.js
Removed ApiSpecBadge component and export; deleted Table-v2 resizable table component entirely; removed ToolHint StyledWrapper wrapper.
Icon Sizing Adjustments
packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/index.js, packages/bruno-app/src/components/RequestPane/WsQueryUrl/index.js (2 files)
Reduced icon sizes from 22 to 20 across refresh, code, save, close, check, and arrow icons.
Workspace UI Updates
packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/*, packages/bruno-app/src/components/WorkspaceHome/WorkspaceOverview/*
Migrated environment action buttons to Button component; updated empty-state layout from flex center to padding-top; removed shared-button CSS block; changed active background from activeBg to surface0.
Import Collection Location & Badge Styling
packages/bruno-app/src/components/Sidebar/ImportCollectionLocation/StyledWrapper.js, packages/bruno-app/src/components/Sidebar/Collections/RemoveCollectionsModal/StyledWrapper.js (2 files)
Updated current-group badge from sidebar.badge.bg to background.surface1; added darken utility for hover states; replaced background colors across remove-collections modal.
Responsive & Utility Components
packages/bruno-app/src/ui/ResponsiveTabs/StyledWrapper.js, packages/bruno-app/src/components/ShareCollection/*
Replaced CSS variable with theme token for tab colors; added no-padding utility class; introduced note-warning styling with rgba background.
Preference Display Simplification
packages/bruno-app/src/components/Preferences/Display/index.js
Removed Theme component import and rendering; now displays only Font section.
Request Tab Theme Integration
packages/bruno-app/src/components/RequestTabs/RequestTab/index.js
Replaced explicit darkTheme/lightTheme imports with useTheme() hook for dynamic theme resolution.
Test Updates
tests/grpc/make-request/make-request.spec.ts, tests/request/save/save.spec.ts, tests/utils/page/locators.ts, tests/websockets/persistence.spec.ts
Replaced accordion locators with list locators for gRPC responses; changed saved-state detection from stroke color to cursor-default class; added await wrappers for async assertions.
Package & Dependency Updates
package.json, packages/bruno-app/package.json, packages/bruno-filestore/package.json
Added ajv (^8.17.1), @opencollection/types (~0.5.0) to root; added jsonschema (^1.5.0) to bruno-app; added storybook workspace script; removed @opencollection/types from bruno-filestore.
Style & Schema Files
packages/bruno-app/src/styles/app.scss, packages/bruno-app/src/styles/globals.css, packages/bruno-app/src/themes/PaletteViewer/*
Removed buttons import from app.scss; removed --color-text-link CSS variable; added Catppuccin and Light Palette Storybook stories with ColorSwatch and ThemeSection components.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • lohit-bruno
  • naman-bruno

🎨 A palette of themes blooms bright,
Dark Frappé meets Catppuccin's light,
With tokens that flow and variants that gleam,
The UI's now unified in every theme! ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: theme + ux overhaul' directly aligns with the PR's primary objective of implementing custom themes and a UX overhaul, as confirmed by the PR description and extensive changeset involving theme configurations, styling updates, and UI component refactoring.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/theme-ux-overhaul

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

❤️ Share

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

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request introduces a comprehensive theme system overhaul for the Bruno application, adding support for multiple custom themes with a schema-based validation system. The changes include 11 new theme variants (both light and dark), a JSON schema for theme validation, enhanced theme provider logic, and UI component updates to use the new theme tokens.

Key Changes

  • Theme System Architecture: Added JSON schema validation for themes and a registry-based theme management system
  • Multiple Theme Variants: Introduced 11 theme variants including Catppuccin (4 variants), VS Code (2 variants), Nord, Monochrome (2 variants), Pastel (2 variants), and the default themes
  • UI Component Updates: Updated Button and ResponsiveTabs components to use new theme tokens and improved button styling consistency

Reviewed changes

Copilot reviewed 121 out of 123 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/bruno-app/src/ui/ResponsiveTabs/StyledWrapper.js Updated tab colors to use new theme token theme.colors.text.subtext0
packages/bruno-app/src/ui/Button/StyledWrapper.js Adjusted button font sizes and added border property support for button variants
packages/bruno-app/src/themes/schema/oss.js Added comprehensive JSON schema defining the structure and required properties for themes
packages/bruno-app/src/themes/schema/index.js Exports the OSS theme schema
packages/bruno-app/src/themes/light/*.js Added 5 new light theme variants (light, light-monochrome, light-pastel, catppuccin-latte, vscode)
packages/bruno-app/src/themes/dark/*.js Added 6 new dark theme variants (dark, dark-monochrome, dark-pastel, catppuccin themes, nord, vscode-dark)
packages/bruno-app/src/themes/index.js Updated theme exports with registry metadata and helper functions for theme filtering
packages/bruno-app/src/styles/globals.css Removed unused CSS variable --color-text-link
packages/bruno-app/src/styles/app.scss Removed file content (buttons import removed)
packages/bruno-app/src/providers/Theme/index.js Enhanced theme provider with variant support, schema validation, and toast notifications
packages/bruno-app/src/providers/ReduxStore/slices/app.js Added preferences tab state management for navigation
packages/bruno-app/src/components/WorkspaceHome/WorkspaceOverview/index.js Replaced custom buttons with Button component for consistency
packages/bruno-app/src/components/WorkspaceHome/WorkspaceOverview/CollectionsList/index.js Updated dropdown item class from dropdown-item-danger to delete-item
packages/bruno-app/src/components/ToolHint/StyledWrapper.js Removed unused styled component file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -1,17 +1,18 @@
import styled from 'styled-components';
import { darken, rgba } from 'polished';
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

Unused import rgba.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link

github-actions bot commented Dec 27, 2025

CLI Test Results

  1 files  ±0  140 suites  ±0   58s ⏱️ +5s
235 tests ±0  235 ✅ ±0  0 💤 ±0  0 ❌ ±0 
301 runs  ±0  300 ✅ ±0  1 💤 ±0  0 ❌ ±0 

Results for commit 10bb2de. ± Comparison against base commit 84f572f.

♻️ This comment has been updated with latest results.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

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

⚠️ Outside diff range comments (7)
packages/bruno-app/src/ui/ResponsiveTabs/StyledWrapper.js (1)

49-49: Inconsistent theming pattern detected.

Line 49 uses props.theme.text while the updated lines (15, 27) now use the more specific props.theme.colors.text.subtext0 pattern. This inconsistency suggests line 49 may have been overlooked during the theme migration.

🔎 Proposed fix to align with new theming pattern
       .content-indicator {
-        color: ${(props) => props.theme.text};
+        color: ${(props) => props.theme.colors.text.subtext0};
       }
packages/bruno-app/src/components/ShareCollection/index.js (1)

34-35: Semantic type mismatch in has property.

The has property name implies a boolean value, but line 35 can return either a number (the length) or false. While this works functionally in the boolean context on line 115, it's semantically inconsistent.

🔎 Suggested fix
-    return {
-      has: collection?.items?.filter(checkItem).length || false,
-      types: [...types]
-    };
+    return {
+      has: Boolean(collection?.items?.filter(checkItem).length),
+      types: [...types]
+    };
packages/bruno-app/src/components/ManageWorkspace/StyledWrapper.js (1)

86-86: Inconsistent theme token migration in the same file.

Lines 86, 159, and 169 use theme.colors.text.muted/danger, while line 110 was migrated to theme.text.muted. Standardize all color tokens within this file to use the same path structure.

Suggested changes:

  • Line 86: Change theme.colors.text.muted to theme.text.muted
  • Line 159: Change theme.colors.text.danger to theme.text.danger
  • Line 169: Change theme.colors.text.muted to theme.text.muted
packages/bruno-app/src/components/ResponsePane/ResponseActions/StyledWrapper.js (1)

8-10: Complete the theme migration for hover states.

Lines 5 and 21 correctly use theme props, but the hover states on Lines 9 and 24 still reference CSS variables. For consistency with the theming overhaul, migrate these to theme-based values as well.

🔎 Proposed fix to complete theme migration
 button {
   color: ${(props) => props.theme.colors.text.subtext0};
   cursor: pointer;

   &:hover {
-    color: var(--color-tab-active);
+    color: ${(props) => props.theme.tabs.active.color};
   }

   &:disabled {
     cursor: not-allowed;
     opacity: 0.5;
   }
 }

 .cursor-pointer {
   display: flex;
   align-items: center;
   color: ${(props) => props.theme.colors.text.subtext0};

   &:hover {
-    color: var(--color-tab-active);
+    color: ${(props) => props.theme.tabs.active.color};
   }
 }

Based on learnings, styled components should use the theme prop to manage CSS colors rather than CSS variables.

Also applies to: 23-25

packages/bruno-app/src/themes/light/light-monochrome.js (1)

260-265: Missing # prefix on color value.

Line 261 has color: '212529' which should be color: '#212529'. This will cause the close button text color to not render correctly.

🔎 Proposed fix
     close: {
-      color: '212529',
+      color: '#212529',
       bg: 'white',
       border: 'white',
       hoverBorder: ''
     },
packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/StyledWrapper.js (1)

47-49: Replace colors.text.blue with an existing theme property. The property colors.text.blue doesn't exist in any theme file. All themes define only: white, green, danger, warning, muted, purple, yellow, subtext0, subtext1, and subtext2. This same issue exists in WsResponsePane as well. Use an appropriate existing color like colors.text.muted or colors.text.warning instead.

packages/bruno-app/src/providers/Theme/index.js (1)

27-33: Memory leak: event listener never removed.

The change event listener is added to the media query but never cleaned up. This will accumulate listeners on re-renders or if the provider remounts.

🔎 Proposed fix
 useEffect(() => {
-  window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', (e) => {
+  const mediaQuery = window.matchMedia('(prefers-color-scheme: light)');
+  const handleChange = (e) => {
     if (storedTheme !== 'system') return;
     setDisplayedTheme(e.matches ? 'light' : 'dark');
     toggleHtml();
-  });
+  };
+  mediaQuery.addEventListener('change', handleChange);
+  return () => mediaQuery.removeEventListener('change', handleChange);
 }, []);

Note: With this fix, consider adding storedTheme to the dependency array so the handler reflects the current stored theme value, or use a ref to avoid stale closure issues.

♻️ Duplicate comments (1)
packages/bruno-app/src/components/Sidebar/ImportCollectionLocation/StyledWrapper.js (1)

2-2: Remove unused import.

The rgba utility is imported but never used in this file. Only darken is utilized for the hover states.

🔎 Proposed fix
-import { darken, rgba } from 'polished';
+import { darken } from 'polished';
🧹 Nitpick comments (16)
packages/bruno-app/src/themes/PaletteViewer/Catppuccin.stories.jsx (1)

153-210: Refactor components to use styled components instead of inline styles.

The ColorSwatch and ThemeSection components use inline styles to define colors, which conflicts with the project's coding guidelines. Per the standards, styled components with the theme prop should manage CSS colors rather than inline styles.

While this is a palette viewer showcasing raw color values, maintaining consistency with the codebase's styling approach is recommended.

As per coding guidelines, styled components should be used as wrappers to define styling, with colors managed via the theme prop.

packages/bruno-app/src/themes/schema/oss.js (1)

1-4: Consider adding $schema property for JSON Schema version clarity.

Adding a $schema property helps document which JSON Schema draft version is being used and can improve tooling support.

🔎 Proposed addition
 export const ossSchema = {
+  $schema: 'http://json-schema.org/draft-07/schema#',
   type: 'object',
   properties: {
     mode: { type: 'string', description: 'Theme mode', enum: ['light', 'dark'] },
packages/bruno-app/src/components/ShareCollection/index.js (1)

118-123: Minor spacing improvement for warning text.

The warning text flows without spacing between "Note:" and the request types list. Adding a space would improve readability.

🔎 Suggested improvement
 <span>
-  Note:
+  Note:{' '}
   {hasNonExportableRequestTypes.types.join(', ')}
   {' '}
   requests in this collection will not be exported
 </span>
packages/bruno-app/src/components/RequestPane/RequestBody/RequestBodyMode/StyledWrapper.js (1)

17-18: Consider migrating hardcoded caret color to theme token.

The .caret selector still uses rgb(140, 140, 140). For consistency with the theme-based approach adopted elsewhere in this PR, consider using a theme token like theme.colors.text.muted.

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

112-116: Hardcoded font size should use theme token.

For consistency with the rest of the file, use the theme's font size tokens.

     p {
       color: ${(props) => props.theme.colors.text.muted};
-      font-size: 13px;
+      font-size: ${(props) => props.theme.font.size.base};
     }
packages/bruno-app/src/components/RequestPane/GrpcBody/index.js (1)

271-283: Array index as key may cause state issues when deleting messages.

When a message is deleted, React may incorrectly preserve state for subsequent items due to index-based keys. Consider using a stable identifier.

-        {messagesToShow.map((message, index) => (
+        {messagesToShow.map((message, index) => {
+          const messageKey = message.name || `msg-${index}`;
+          return (
           <SingleGrpcMessage
-            key={index}
+            key={messageKey}
             message={message}
             ...
           />
+          );
+        })}
-        ))}

Alternatively, add a unique id field when creating messages.

packages/bruno-app/src/themes/PaletteViewer/PaletteViewer.stories.jsx (2)

174-182: Hue extraction doesn't handle hex colors.

The PRIMARY accent uses hex #bd7a28, but the hue regex only matches hsl() format. This causes PRIMARY to sort with hue 0°.

     const sorted = [...accents].sort((a, b) => {
-      const hueA = parseInt(a[1].match(/hsl\((\d+)/)?.[1] || 0);
-      const hueB = parseInt(b[1].match(/hsl\((\d+)/)?.[1] || 0);
+      const getHue = (color) => {
+        const hslMatch = color.match(/hsl\((\d+)/);
+        if (hslMatch) return parseInt(hslMatch[1]);
+        // For hex, approximate hue (or exclude from sorting)
+        return 0;
+      };
+      const hueA = getHue(a[1]);
+      const hueB = getHue(b[1]);
       return hueA - hueB;
     });

Consider converting PRIMARY to hsl() for consistency with other accents, or enhance the hue extraction.


144-172: Text story duplicates ColorSwatch.

The Text story renders swatches inline instead of reusing the ColorSwatch component defined at line 55.

export const Text = {
  render: () => (
    <div style={{ padding: '24px', backgroundColor: '#ffffff' }}>
      <h2 style={{ fontSize: '20px', fontWeight: 600, marginBottom: '16px', color: '#343434' }}>
        Text Colors
      </h2>
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: '16px' }}>
        {Object.entries(palette.text).map(([name, color]) => (
-          <div key={name} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '8px' }}>
-            ...
-          </div>
+          <ColorSwatch key={name} name={name} color={color} />
        ))}
      </div>
    </div>
  )
};
packages/bruno-app/src/components/Dropdown/StyledWrapper.js (1)

140-155: Review the necessity of !important flags.

Multiple !important declarations suggest potential CSS specificity conflicts. While they ensure the active state takes precedence, consider whether the selector specificity could be adjusted to avoid relying on !important.

That said, if this resolves existing specificity battles with global styles or Tailwind utilities, the current approach is acceptable.

packages/bruno-app/src/themes/dark/catppuccin-mocha.js (1)

173-173: Consider using color constants for rgba values.

Several places use hardcoded rgba values (e.g., 'rgba(108, 112, 134, 0.16)' on line 173) instead of referencing the defined color constants. For maintainability and consistency, consider using the rgba helper from polished with the color constants:

-    hoverBg: 'rgba(108, 112, 134, 0.16)',
+    hoverBg: rgba(colors.OVERLAY0, 0.16),

However, if these specific rgba values are intentional overrides or require exact color matching that differs from the named constants, the current approach is acceptable.

Also applies to: 219-219, 382-382, 397-399, 424-424, 499-499, 538-538, 563-563, 576-576, 619-619, 623-623

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

6-21: Hardcoded colors in ThemePreview.

The ThemePreview component uses hardcoded color values instead of theme tokens. While this might be intentional to show a generic preview, consider whether these should reflect the actual theme colors being previewed.

If the preview is meant to be a generic representation, the current implementation is acceptable. However, if it should dynamically reflect the selected theme's actual colors, this would need refactoring.


73-103: Add id attributes for proper label-input association.

The radio inputs lack id attributes, which means the <label> elements are relying on implicit association through wrapping. While this works, explicit id/htmlFor associations improve accessibility and are a best practice.

🔎 Proposed enhancement for accessibility
 <label className="theme-mode-option">
   <input
     type="radio"
+    id="theme-mode-light"
     name="theme-mode"
     value="light"
     checked={storedTheme === 'light'}
     onChange={() => handleModeChange('light')}
   />
   <span>Light</span>
 </label>
 <label className="theme-mode-option">
   <input
     type="radio"
+    id="theme-mode-dark"
     name="theme-mode"
     value="dark"
     checked={storedTheme === 'dark'}
     onChange={() => handleModeChange('dark')}
   />
   <span>Dark</span>
 </label>
 <label className="theme-mode-option">
   <input
     type="radio"
+    id="theme-mode-system"
     name="theme-mode"
     value="system"
     checked={storedTheme === 'system'}
     onChange={() => handleModeChange('system')}
   />
   <span>System</span>
 </label>
packages/bruno-app/src/themes/dark/dark-pastel.js (1)

78-82: Consider using named color constants for overlay values.

Lines 80-81 use hardcoded hex values '#555555' and '#444444' instead of named constants from the colors object. For consistency with the rest of the theme (where all colors reference named constants), consider defining these values in the colors object.

🔎 Proposed refactor for consistency
 const colors = {
   // Primary palette - glowing pastels against deep purple-black
   BRAND: '#f0a6ca', // Soft rose - warm and inviting
   TEXT: '#e8e0f0', // Lavender white - soft, readable
   TEXT_MUTED: '#a89fc4', // Dusty lavender
   TEXT_LINK: '#a8c5f0', // Soft periwinkle blue
   BG: '#1a1625', // Deep plum-black

   // Core colors
   WHITE: '#ffffff',
   BLACK: '#0d0a12',
   SLATE: '#e8e0f0',

   // Luminous pastels - glowing against darkness
   GREEN: '#7dd3a8', // Mint glow
   YELLOW: '#f0d77d', // Soft gold
   RED: '#f0887d', // Coral blush
   PURPLE: '#c4a6f0', // Lavender dream
   BLUE: '#7db8f0', // Sky shimmer
   PINK: '#f0a6ca', // Rose petal
   ORANGE: '#f0b87d', // Peach sunset
   TEAL: '#7dd3c9', // Aqua glow
   MAGENTA: '#e09fd9', // Orchid

   // Deep grayscale with purple undertones
   GRAY_1: '#1f1a2e', // Deepest plum
   GRAY_2: '#2a2440', // Dark violet
   GRAY_3: '#352e4d', // Muted purple
   GRAY_4: '#453d5c', // Dusty violet
   GRAY_5: '#5c5478', // Medium violet
   GRAY_6: '#7a7294', // Soft violet
   GRAY_7: '#9890ad', // Light violet
   GRAY_8: '#b8b0cc', // Pale violet
   GRAY_9: '#d8d0e8', // Whisper violet
-  GRAY_10: '#f0e8ff', // Lightest violet
+  GRAY_10: '#f0e8ff', // Lightest violet
+  OVERLAY_GRAY_1: '#555555',
+  OVERLAY_GRAY_2: '#444444',

   // CodeMirror syntax - a constellation of colors
   CODEMIRROR_TOKENS: {
     DEFINITION: '#7dd3a8', // Mint - definitions stand out fresh
     PROPERTY: '#7db8f0', // Sky blue - clear and calm
     STRING: '#f0a6ca', // Rose - strings feel warm
     NUMBER: '#7dd3c9', // Teal - numbers are precise
     ATOM: '#c4a6f0', // Lavender - atoms are special
     VARIABLE: '#a8c5f0', // Periwinkle - variables flow
     KEYWORD: '#e09fd9', // Orchid - keywords command attention
     COMMENT: '#7a7294', // Muted violet - comments recede
     OPERATOR: '#b8b0cc' // Pale violet - operators connect
   }
 };

 const darkPastelTheme = {
   // ... other properties ...

   overlay: {
     overlay2: colors.GRAY_6,
-    overlay1: '#555555',
-    overlay0: '#444444'
+    overlay1: colors.OVERLAY_GRAY_1,
+    overlay0: colors.OVERLAY_GRAY_2
   },
packages/bruno-app/src/components/StatusBar/ThemeDropdown/index.js (1)

173-173: Simplify onClick handler.

The ternary expression isOpen ? handleClose() : handleOpen() can be simplified by directly toggling based on isOpen or using a single handler function.

🔎 Proposed simplification
+  const handleToggle = () => {
+    if (isOpen) {
+      handleClose();
+    } else {
+      handleOpen();
+    }
+  };
+
   return (
     <ToolHint text="Theme" toolhintId="ThemeDropdown" place="top" offset={10} hidden={!tooltipEnabled}>
       <Tippy
         content={menuContent}
         placement="top-start"
         interactive={true}
         arrow={false}
         animation={false}
         visible={isOpen}
         onClickOutside={handleClose}
         appendTo="parent"
       >
-        <div onClick={() => isOpen ? handleClose() : handleOpen()}>
+        <div onClick={handleToggle}>
           {children}
         </div>
       </Tippy>
     </ToolHint>
   );
packages/bruno-app/src/components/RequestPane/WsBody/index.js (1)

16-16: Document or configure the hardcoded feature flag.

The canClientSendMultipleMessages constant is hardcoded to false. If this is a work-in-progress feature flag, consider:

  1. Adding a comment explaining why it's disabled and when it will be enabled
  2. Moving it to a configuration file or feature flags system
  3. Deriving it from collection/item settings if it should be configurable per request
packages/bruno-app/src/themes/dark/dark.js (1)

2-3: Remove commented-out dead code.

Line 2 is a commented duplicate of the BRAND color definition on line 3. This appears to be leftover from development.

🔎 Proposed fix
 const colors = {
-  // BRAND: '#d9a342',
   BRAND: '#d9a342',

This was referenced Dec 27, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (10)
tests/websockets/persistence.spec.ts (3)

9-13: Extract the duplicated helper to a shared utility.

The TODO comment correctly identifies that isRequestSaved should be moved to a common location. This function is duplicated in tests/request/save/save.spec.ts (lines 6-9) with identical implementation, violating DRY principles.

Consider extracting it to tests/utils/page/helpers.ts or similar. Would you like me to generate the extraction?


58-58: Remove unnecessary await on synchronous assertions.

The await keyword before expect() is redundant here since .toBe() is a synchronous matcher. The pattern should be:

-await expect(await isRequestSaved(locators.saveButton())).toBe(false);
+expect(await isRequestSaved(locators.saveButton())).toBe(false);

And:

-await expect(result).toBe(true);
+expect(result).toBe(true);

Also applies to: 63-63


37-67: Add test.step for better test reporting.

Per coding guidelines, test.step should be promoted "as much as possible" to make generated reports easier to read. Consider breaking down this test into logical steps:

🔎 Example refactor with test.step
 test('save new websocket url', async ({ pageWithUserData: page }) => {
   const replacementUrl = 'ws://localhost:8083';
   const locators = buildWebsocketCommonLocators(page);

   const clearText = async (text: string) => {
     for (let i = text.length; i > 0; i--) {
       await page.keyboard.press('Backspace');
     }
   };

-  await page.locator('#sidebar-collection-name').click();
-  await page.getByTitle(BRU_REQ_NAME).click();
+  await test.step('Open the websocket request', async () => {
+    await page.locator('#sidebar-collection-name').click();
+    await page.getByTitle(BRU_REQ_NAME).click();
+  });

-  // remove the original url from the request
-  await page.locator('.input-container').filter({ hasText: originalUrl }).first().click();
-  await clearText(originalUrl);
-  // replace it with an arbritrary url
-  await page.keyboard.insertText(replacementUrl);
+  await test.step('Update websocket URL', async () => {
+    await page.locator('.input-container').filter({ hasText: originalUrl }).first().click();
+    await clearText(originalUrl);
+    await page.keyboard.insertText(replacementUrl);
+  });

-  // check if the request is now unsaved
-  await expect(await isRequestSaved(locators.saveButton())).toBe(false);
+  await test.step('Verify request is marked as unsaved', async () => {
+    expect(await isRequestSaved(locators.saveButton())).toBe(false);
+  });

-  await locators.saveButton().click();
-  const result = await waitForPredicate(() => isRequestSaved(locators.saveButton()));
-  await expect(result).toBe(true);
+  await test.step('Save the request', async () => {
+    await locators.saveButton().click();
+    const result = await waitForPredicate(() => isRequestSaved(locators.saveButton()));
+    expect(result).toBe(true);
+  });

-  // check if the replacementUrl is now visually available
-  await expect(page.locator('.input-container').filter({ hasText: replacementUrl }).first()).toBeAttached();
+  await test.step('Verify new URL is persisted', async () => {
+    await expect(page.locator('.input-container').filter({ hasText: replacementUrl }).first()).toBeAttached();
+  });
 });

Based on coding guidelines.

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

6-9: Extract the duplicated helper to a shared utility.

This isRequestSaved function is duplicated in tests/websockets/persistence.spec.ts (lines 10-13) with identical implementation. Extract to a shared test utility for maintainability.


64-64: Remove unnecessary await on synchronous assertions.

The await keyword before expect() is redundant since .toBe() is synchronous:

-await expect(await isRequestSaved(locators.saveButton())).toBe(false);
+expect(await isRequestSaved(locators.saveButton())).toBe(false);

And:

-await expect(result).toBe(true);
+expect(result).toBe(true);

Also applies to: 71-71


38-72: Add test.step for better test reporting.

Per coding guidelines, wrap test actions in test.step blocks to improve report readability. This test has distinct phases (setup, modify URL, verify unsaved, save, verify saved) that would benefit from step organization.

🔎 Example refactor with test.step
 test('saves new http request', async ({ page, createTmpDir }) => {
-  // prep the collection by creating a new collection and a new http request
-  await setup(page, createTmpDir);
+  await test.step('Setup collection and request', async () => {
+    await setup(page, createTmpDir);
+  });

   const locators = buildCommonLocators(page);
   const originalUrl = 'https://echo.usebruno.com';
   const replacementUrl = 'ws://localhost:8082';

   const clearText = async (text: string) => {
     for (let i = text.length; i > 0; i--) {
       await page.keyboard.press('Backspace');
     }
   };

-  // Open the request tab
-  await page.locator('.collection-item-name').filter({ hasText: 'test-request' }).dblclick();
-  await expect(page.locator('.request-tab .tab-label').filter({ hasText: 'test-request' })).toBeVisible();
+  await test.step('Open request tab', async () => {
+    await page.locator('.collection-item-name').filter({ hasText: 'test-request' }).dblclick();
+    await expect(page.locator('.request-tab .tab-label').filter({ hasText: 'test-request' })).toBeVisible();
+  });

-  // remove the original url from the request
-  await page.locator('.input-container').filter({ hasText: originalUrl }).first().click();
-  await clearText(originalUrl);
-  // replace it with an arbitrary url
-  await page.keyboard.insertText(replacementUrl);
+  await test.step('Modify request URL', async () => {
+    await page.locator('.input-container').filter({ hasText: originalUrl }).first().click();
+    await clearText(originalUrl);
+    await page.keyboard.insertText(replacementUrl);
+  });

-  // check if the request is now unsaved
-  await expect(await isRequestSaved(locators.saveButton())).toBe(false);
+  await test.step('Verify request is marked as unsaved', async () => {
+    expect(await isRequestSaved(locators.saveButton())).toBe(false);
+  });

-  // trigger a save
-  locators.saveButton().click();
-  // Wait for it to be saved
-  const result = await waitForPredicate(() => isRequestSaved(locators.saveButton()));
-  await expect(result).toBe(true);
+  await test.step('Save and verify persistence', async () => {
+    locators.saveButton().click();
+    const result = await waitForPredicate(() => isRequestSaved(locators.saveButton()));
+    expect(result).toBe(true);
+  });
 });

Based on coding guidelines.

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

32-98: Consider deriving themeRegistry from theme objects to reduce duplication.

The themeRegistry duplicates all theme IDs from the themes object, creating a maintenance burden. If a theme is added or removed, both structures must be manually synchronized.

🔎 Proposed refactor to derive metadata from theme objects

Each theme object could include its own metadata (id, name, mode), then themeRegistry can be derived:

+// In each theme file (e.g., light/light.js), add metadata:
+const lightTheme = {
+  id: 'light',
+  name: 'Bruno Light',
+  mode: 'light',
+  brand: colors.BRAND,
+  // ... rest of theme
+};

+// Then derive themeRegistry:
-export const themeRegistry = {
-  'light': {
-    id: 'light',
-    name: 'Bruno Light',
-    mode: 'light'
-  },
-  // ... all other themes
-};
+export const themeRegistry = Object.keys(themes).reduce((registry, key) => {
+  const theme = themes[key];
+  registry[key] = {
+    id: theme.id || key,
+    name: theme.name,
+    mode: theme.mode
+  };
+  return registry;
+}, {});

This ensures single source of truth and reduces the chance of inconsistencies.


100-101: Consider memoizing the theme filter results.

The helper functions recreate arrays on every call. If these are called frequently, consider computing the results once or memoizing them.

🔎 Optional memoization approach
-export const getLightThemes = () => Object.values(themeRegistry).filter((t) => t.mode === 'light');
-export const getDarkThemes = () => Object.values(themeRegistry).filter((t) => t.mode === 'dark');
+const lightThemes = Object.values(themeRegistry).filter((t) => t.mode === 'light');
+const darkThemes = Object.values(themeRegistry).filter((t) => t.mode === 'dark');
+
+export const getLightThemes = () => lightThemes;
+export const getDarkThemes = () => darkThemes;

This avoids recreating arrays on every call, though the performance gain is minimal unless called very frequently.

packages/bruno-app/src/themes/light/light-monochrome.js (1)

1-36: Semantic color naming is misleading in monochrome context.

Color constants like GREEN (line 11) and YELLOW (line 12) are defined as gray values (#525252), which creates semantic confusion. While this achieves the monochrome effect, developers might expect these constants to represent actual green and yellow colors.

🔎 Proposed semantic improvement

Consider using semantically neutral names for grayscale values:

 const colors = {
   BRAND: '#525252',
   TEXT: 'rgb(52, 52, 52)',
   TEXT_MUTED: '#737373',
   TEXT_LINK: '#404040',
   BACKGROUND: '#fff',

   WHITE: '#fff',
   BLACK: '#000',
   SLATE_BLACK: '#343434',
-  GREEN: '#525252',
-  YELLOW: '#525252',
+  ACCENT_PRIMARY: '#525252',
+  ACCENT_SECONDARY: '#525252',

   // ... and update references below
-  yellow: colors.YELLOW,
+  yellow: colors.ACCENT_SECONDARY,

This makes the intent clearer and avoids confusion about expected color values.

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

186-186: Consider optional chaining for defensive programming.

While body.grpc.length is currently safe due to the parent guard (line 245), adding optional chaining would make this more resilient to future refactoring:

-  const isSingleMessage = !canClientSendMultipleMessages || body.grpc.length === 1;
+  const isSingleMessage = !canClientSendMultipleMessages || body.grpc?.length === 1;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4ef4e21 and 10bb2de.

📒 Files selected for processing (11)
  • packages/bruno-app/src/components/Preferences/index.js
  • packages/bruno-app/src/components/RequestPane/GrpcBody/index.js
  • packages/bruno-app/src/components/TagList/StyledWrapper.js
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js
  • packages/bruno-app/src/themes/index.js
  • packages/bruno-app/src/themes/light/light-monochrome.js
  • packages/bruno-app/src/themes/light/light.js
  • tests/grpc/make-request/make-request.spec.ts
  • tests/request/save/save.spec.ts
  • tests/utils/page/locators.ts
  • tests/websockets/persistence.spec.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/bruno-app/src/components/Preferences/index.js
  • packages/bruno-app/src/themes/light/light.js
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

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

Files:

  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js
  • packages/bruno-app/src/components/RequestPane/GrpcBody/index.js
  • tests/grpc/make-request/make-request.spec.ts
  • tests/websockets/persistence.spec.ts
  • packages/bruno-app/src/themes/index.js
  • tests/utils/page/locators.ts
  • tests/request/save/save.spec.ts
  • packages/bruno-app/src/themes/light/light-monochrome.js
  • packages/bruno-app/src/components/TagList/StyledWrapper.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 existing expect() playwright calls

  • Avoid using page.pause() in code

  • Use locator variables for locators

  • Avoid using test.only

  • Use multiple assertions

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

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

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

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

Files:

  • tests/grpc/make-request/make-request.spec.ts
  • tests/websockets/persistence.spec.ts
  • tests/utils/page/locators.ts
  • tests/request/save/save.spec.ts
🧠 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 **/*.{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/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js
  • packages/bruno-app/src/components/RequestPane/GrpcBody/index.js
  • packages/bruno-app/src/themes/index.js
  • packages/bruno-app/src/themes/light/light-monochrome.js
  • packages/bruno-app/src/components/TagList/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 Component CSS might also change layout but Tailwind classes shouldn't define colors

Applied to files:

  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js
  • packages/bruno-app/src/components/TagList/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/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js
  • packages/bruno-app/src/components/TagList/StyledWrapper.js
📚 Learning: 2025-12-17T21:41:24.730Z
Learnt from: naman-bruno
Repo: usebruno/bruno PR: 6407
File: packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js:5-41
Timestamp: 2025-12-17T21:41:24.730Z
Learning: Do not suggest PropTypes validation for React components in the Bruno codebase. The project does not use PropTypes, so reviews should avoid proposing PropTypes and rely on the existing typing/validation approach (e.g., TypeScript or alternative runtime checks) if applicable. This guideline applies broadly to all JavaScript/JSX components in the repo.

Applied to files:

  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js
  • packages/bruno-app/src/components/RequestPane/GrpcBody/index.js
  • packages/bruno-app/src/themes/index.js
  • packages/bruno-app/src/themes/light/light-monochrome.js
  • packages/bruno-app/src/components/TagList/StyledWrapper.js
🧬 Code graph analysis (3)
packages/bruno-app/src/components/RequestPane/GrpcBody/index.js (1)
packages/bruno-app/src/ui/Button/index.js (1)
  • Button (4-79)
tests/websockets/persistence.spec.ts (1)
tests/utils/wait.ts (1)
  • waitForPredicate (4-13)
packages/bruno-app/src/themes/light/light-monochrome.js (1)
packages/bruno-app/src/themes/light/light.js (1)
  • colors (52-64)
⏰ 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: SSL Tests - macOS
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: CLI Tests
  • GitHub Check: Playwright E2E Tests
🔇 Additional comments (10)
packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js (1)

12-12: LGTM! Border declarations are now consistent.

All border declarations correctly use the solid 1px prefix with theme.border.border0, resolving the previous inconsistency. The theming refactor is well-executed and uniform across the table structure.

Also applies to: 48-49, 66-67

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

29-30: LGTM — improved semantic clarity.

The theme token changes align well with the theming overhaul. Moving from requestTabs.active.bg to background.surface2 is semantically clearer for tag styling, and the border color now consistently matches the base state (line 18).

tests/grpc/make-request/make-request.spec.ts (1)

91-91: LGTM! Locator update aligns with UI refactor.

The update from accordion() to list() is consistent with the locator definition change and properly reflects the UI component refactor from accordion to list.

Also applies to: 193-193

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

198-198: Locator rename verified and complete.

The refactor from accordion to list is clean with no orphaned references. The new test ID grpc-responses-list is properly implemented in the component code at packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/GrpcQueryResult/index.js:66, and all references have been updated consistently.

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

1-29: LGTM!

The theme imports and registration structure is clean and follows coding standards. The mix of plain identifiers and kebab-case string keys appropriately handles the different theme naming conventions.

packages/bruno-app/src/themes/light/light-monochrome.js (2)

38-92: LGTM!

The theme object is properly renamed to lightMonochromeTheme with mode: 'light' metadata. The new structure with accents, background, and overlay sections provides good organization for the monochrome palette.


418-617: LGTM!

The remaining component theme definitions consistently apply the monochrome palette throughout. The export statement correctly uses lightMonochromeTheme. All coding standards are followed.

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

245-262: LGTM—Empty state Button usage is consistent.

The empty state correctly uses the UI Button component with appropriate props, aligning with the PR's component standardization objective.


225-230: LGTM—Auto-scroll behavior is implemented correctly.

The useEffect properly scrolls to the latest message when new messages are added, with the correct dependency array.


264-286: LGTM—Message filtering and rendering logic is correct.

The messagesToShow filter correctly handles both single and multiple message scenarios. The mapping indices align with the original body.grpc array indices because the filter only ever removes messages from position 1 onward (when canClientSendMultipleMessages is false), preserving the index correspondence for operations like onEdit and onDeleteMessage.

Comment on lines +20 to +69
const MessageToolbar = ({
index,
canClientStream,
isConnectionActive,
onSend,
onRegenerateMessage,
onPrettify,
onDeleteMessage,
showDelete
}) => {
return (
<div className="message-toolbar">
<span className="message-label">Message {index + 1}</span>
<div className="toolbar-actions mr-2">
<ToolHint text="Format JSON" toolhintId={`prettify-msg-${index}`}>
<button onClick={onPrettify} className="toolbar-btn">
<IconWand size={16} strokeWidth={1.5} />
</button>
</ToolHint>

<ToolHint text="Generate sample" toolhintId={`regenerate-msg-${index}`}>
<button onClick={onRegenerateMessage} className="toolbar-btn">
<IconRefresh size={16} strokeWidth={1.5} />
</button>
</ToolHint>

{canClientStream && (
<ToolHint text={isConnectionActive ? 'Send message' : 'Connection not active'} toolhintId={`send-msg-${index}`}>
<button
onClick={onSend}
disabled={!isConnectionActive}
className={`toolbar-btn ${!isConnectionActive ? 'disabled' : ''}`}
data-testid={`grpc-send-message-${index}`}
>
<IconSend size={16} strokeWidth={1.5} />
</button>
</ToolHint>
)}

{showDelete && (
<ToolHint text="Delete message" toolhintId={`delete-msg-${index}`}>
<button onClick={onDeleteMessage} className="toolbar-btn delete">
<IconTrash size={16} strokeWidth={1.5} />
</button>
</ToolHint>
)}
</div>
</div>
);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace native buttons with the UI Button component for consistency.

The MessageToolbar uses native <button> elements (lines 35, 41, 48, 61), which contradicts the PR objective of "replacing native buttons with a shared Button component." The empty state (line 250) and footer (line 290) correctly use the Button component—the toolbar should follow the same pattern.

🔎 Example refactor for the prettify button
-        <ToolHint text="Format JSON" toolhintId={`prettify-msg-${index}`}>
-          <button onClick={onPrettify} className="toolbar-btn">
-            <IconWand size={16} strokeWidth={1.5} />
-          </button>
-        </ToolHint>
+        <ToolHint text="Format JSON" toolhintId={`prettify-msg-${index}`}>
+          <Button
+            onClick={onPrettify}
+            variant="ghost"
+            size="sm"
+            icon={<IconWand size={16} strokeWidth={1.5} />}
+          />
+        </ToolHint>

Apply the same pattern to the other three buttons (regenerate, send, delete).

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

🤖 Prompt for AI Agents
In packages/bruno-app/src/components/RequestPane/GrpcBody/index.js around lines
20 to 69, replace the native <button> elements used for prettify, regenerate,
send and delete with the shared Button component to match the rest of the PR:
import the Button component at the top, then for each ToolHint child use
<Button> with the same onClick handlers (onPrettify, onRegenerateMessage,
onSend, onDeleteMessage), pass disabled for the send button when
!isConnectionActive, preserve className="toolbar-btn" (and "delete" for the
delete button) and data-testid={`grpc-send-message-${index}`}, and keep the icon
children exactly as-is so visual/behavioral parity is maintained. Ensure the
send button still applies the conditional disabled state and styling and that
all buttons keep their original aria/test ids and props.

@bijin-bruno bijin-bruno merged commit f40e4d2 into main Dec 27, 2025
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants