Skip to content

feat: add response data type selector in response viewer#6100

Merged
bijin-bruno merged 26 commits intousebruno:mainfrom
abhishek-bruno:feat/response-data-type-selector
Dec 9, 2025
Merged

feat: add response data type selector in response viewer#6100
bijin-bruno merged 26 commits intousebruno:mainfrom
abhishek-bruno:feat/response-data-type-selector

Conversation

@abhishek-bruno
Copy link
Member

@abhishek-bruno abhishek-bruno commented Nov 14, 2025

Description

Added Response Data Type selector with preview mode for each type. Closes #4963
JIRA

  • Introduced a new ButtonDropdown component for selecting options.
  • Integrated ButtonDropdown into QueryResultTypeSelector for format selection.
  • Enhanced QueryResult to support multiple preview formats including JSON, HTML, XML, and more.
  • Added new preview components for HTML, JSON, XML, Text, and more.
  • Updated formatResponse utility to handle new formats and prettify HTML/JavaScript.
  • Adjusted QueryResult styling for better layout and usability.

Contribution Checklist:

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

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

Publishing to New Package Managers

Please see here for more information.

Summary by CodeRabbit

  • New Features

    • Expanded response format selector with Editor/Preview toggle and many preview modes (JSON, HTML, XML, JavaScript, Text/Raw, Hex, Base64, media)
    • Rich, safer previews: formatted HTML/JS, JSON tree view, collapsible XML, text fallback, video/audio/image/PDF playback, and download action
    • New responsive actions menu and unified response view with format/tab controls
  • Tests

    • End-to-end tests plus new test helpers and locators for format selection, preview flows, and response actions

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

@abhishek-bruno abhishek-bruno force-pushed the feat/response-data-type-selector branch 2 times, most recently from b7fba59 to 01624b2 Compare November 20, 2025 08:37
@abhishek-bruno abhishek-bruno marked this pull request as draft November 20, 2025 10:27
@abhishek-bruno abhishek-bruno force-pushed the feat/response-data-type-selector branch from dc5bcea to d079708 Compare December 1, 2025 06:40
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 2, 2025

Walkthrough

Adds response format detection, formatting utilities, selector UI, multiple preview components (HTML/JSON/XML/Text/Video), response action hooks/components (copy/clear/download/layout), QueryResponse wrapper, and Playwright tests/fixtures for format selection and previews.

Changes

Cohort / File(s) Summary
Preview components
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js, .../JsonPreview.js, .../TextPreview.js, .../VideoPreview.js, .../XmlPreview/index.js, .../XmlPreview/StyledWrapper.js
New memoized preview components: HtmlPreview (base tag injection, drag-disable via MutationObserver), JsonPreview (validation, ErrorAlert, ReactJson), TextPreview (safe stringify), VideoPreview (base64→Blob URL lifecycle), XmlPreview (DOM parse → collapsible tree).
Response utils & formatters
packages/bruno-app/src/utils/response/index.js, packages/bruno-app/src/utils/common/index.js, packages/bruno-app/src/utils/common/codemirror.js
New content-type normalization, buffer magic-number detection, isValidHtml/snippet, prettifyHtml/prettifyJavaScript (Prettier), hex dump/isHex utilities; updated CodeMirror mode mapping (javascript).
QueryResult → QueryResponse & hooks
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js, packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js, packages/bruno-app/src/components/ResponsePane/QueryResponse/StyledWrapper.js
Introduced useInitialResponseFormat and useResponsePreviewFormatOptions; refactored preview control to selectedFormat/selectedTab and previewMode/codeMirrorMode; added QueryResponse wrapper that surfaces selector and preview props.
Selector & dropdown UI
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx, .../StyledWrapper.js, packages/bruno-app/src/ui/ButtonDropdown/index.jsx, .../StyledWrapper.js
New QueryResultTypeSelector (toggle + ButtonDropdown) and ButtonDropdown implementation with styling.
Response actions & hooks
packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js, .../ResponsePaneActions/StyledWrapper.js, .../ResponseDownload/index.js, .../ResponseDownload/StyledWrapper.js, .../ResponseCopy/index.js, .../ResponseClear/index.js, .../ResponseBookmark/index.js, .../ResponseLayoutToggle/index.js
New responsive actions UI and hooks (useResponseCopy, useResponseClear, useResponseLayoutToggle); ResponseDownload replaces ResponseSave; action components accept children and keyboard interactions; unified styled wrappers.
Removed component
packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js, .../ResponseSave/StyledWrapper.js
Deleted ResponseSave and its styled wrapper; download behavior moved to ResponseDownload.
Error UI
packages/bruno-app/src/ui/ErrorAlert/index.js, packages/bruno-app/src/ui/ErrorAlert/StyledWrapper.js
New ErrorAlert component and styled wrapper used by preview components for parse/validation errors.
Integration & imports
packages/bruno-app/src/components/ResponsePane/index.js, packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/index.js, packages/bruno-app/src/components/ResponsePane/Timeline/.../Body/index.js, packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js
Replaced QueryResult usages with QueryResponse, wired selector/actions into ResponsePane, propagated selectedFormat/selectedTab props, and adjusted imports/layout.
Tests, fixtures & helpers
tests/utils/page/actions.ts, tests/utils/page/locators.ts, tests/response/response-format-select-and-preview/*.spec.ts, tests/response/response-format-select-and-preview/fixtures/*, tests/response/large-response-crash-prevention.spec.ts, various tests/response-examples/*
New E2E Playwright suite and fixtures for format selection/preview; updated locators and helpers (collection-first openRequest, sendRequestAndWaitForResponse, switchResponseFormat, switchToPreviewTab, switchToEditorTab, clickResponseAction); one tightened test assertion.
Styling & minor UI tweaks
many packages/bruno-app/src/components/ResponsePane/*/StyledWrapper.js and related index files
Numerous cosmetic style/token adjustments: font-weight, margins, hover/bg, border-radius, separator class, and small className changes across response components.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant ResponsePane
    participant Detector as ResponseUtils
    participant QueryResponse
    participant Selector as TypeSelector
    participant Preview as PreviewComponent

    User->>ResponsePane: response arrives (dataBuffer, headers)
    ResponsePane->>Detector: detectContentTypeFromBuffer / getDefaultResponseFormat
    Detector-->>ResponsePane: initialFormat, previewOptions
    ResponsePane->>QueryResponse: render(selectedFormat, selectedTab, codeMirrorMode)
    QueryResponse->>Selector: render format selector
    User->>Selector: choose format
    Selector->>QueryResponse: onFormatChange(format)
    QueryResponse->>Preview: mount appropriate PreviewComponent
    Preview->>Detector: validate/format (isValidHtml / JSON.parse / prettify / formatHexView)
    Preview-->>User: display rendered preview or ErrorAlert
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Focus areas:
    • format-detection utilities and magic-number mapping (packages/.../utils/response/index.js)
    • HtmlPreview: base tag injection, MutationObserver lifecycle and drag-state handling
    • JsonPreview/ErrorAlert integration and JSON parsing/error surfaces
    • VideoPreview: base64→Blob conversion and object URL cleanup
    • Hooks: useInitialResponseFormat / useResponsePreviewFormatOptions and prop synchronization between ResponsePane → QueryResponse
    • Tests: new Playwright helpers, locators, and the E2E suite

Possibly related PRs

Suggested reviewers

  • lohit-bruno
  • naman-bruno

Poem

A pane that learns what bytes can be,
HTML, JSON, video — pick and see,
Helpers tidy, previews play,
Tests keep watch to guard the way,
Tiny hooks make the UI free. 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.94% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add response data type selector in response viewer' is concise, descriptive, and accurately reflects the main feature added—a selector for choosing response data types in the response viewer.
Linked Issues check ✅ Passed The PR fully implements the objectives from issue #4963: enables response data type selection, supports multiple preview formats (JSON, HTML, XML, Text), and provides an in-app mechanism to switch formats without external tools.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the response data type selector feature. Supporting changes include new preview components, format utilities, ButtonDropdown UI component, test utilities, and styling adjustments—all necessary for the core feature.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent 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 1db2ef6 and da74540.

📒 Files selected for processing (1)
  • tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/response/response-format-select-and-preview/response-format-select-and-preview.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: SSL Tests - Linux
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: Unit Tests
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: CLI Tests

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

@abhishek-bruno abhishek-bruno marked this pull request as ready for review December 2, 2025 14:15
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: 12

🧹 Nitpick comments (18)
tests/response/large-response-crash-prevention.spec.ts (1)

14-42: Consider adding test.step for better report readability.

Per coding guidelines, wrapping logical sections (create collection, create request, send request, verify warning) in test.step would improve generated report clarity.

packages/bruno-app/src/utils/common/index.js (3)

327-424: Consider extracting duplicated large-response handling logic.

The same pattern appears for html, javascript, and text/raw modes:

if (isVeryLargeResponse) {
  if (typeof data === 'string') { return data; }
  if (data === null || data === undefined) { return String(data); }
  if (typeof data === 'object') { return safeStringifyJSON(data, false); }
  return String(data);
}

Extract to a helper function to reduce duplication:

+const handleLargeResponse = (data) => {
+  if (typeof data === 'string') return data;
+  if (data === null || data === undefined) return String(data);
+  if (typeof data === 'object') return safeStringifyJSON(data, false);
+  return String(data);
+};
+
 // Then use in each mode:
 if (mode.includes('html')) {
-  if (isVeryLargeResponse) {
-    if (typeof data === 'string') {
-      return data;
-    }
-    if (data === null || data === undefined) {
-      return String(data);
-    }
-    if (typeof data === 'object') {
-      return safeStringifyJSON(data, false);
-    }
-    return String(data);
-  }
+  if (isVeryLargeResponse) return handleLargeResponse(data);

450-466: Inconsistent function declaration style.

These new functions use function keyword while the rest of the file uses arrow function exports (e.g., export const simpleHash = (str) => {). Consider using arrow functions for consistency.

-export function prettifyHtmlString(htmlString) {
+export const prettifyHtmlString = (htmlString) => {
   if (typeof htmlString !== 'string') return htmlString;
   // ...
-};
+};

385-400: Empty string return on error may hide formatting issues.

When hex encoding fails, returning '' silently could confuse users. Consider logging a warning or returning a more informative fallback.

       } catch (stringError) {
-        return '';
+        console.warn('Failed to format hex view:', stringError);
+        return '';
       }
     }
-    return '';
+    console.warn('Failed to format hex view for non-string data');
+    return '';
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/TextPreview.js (1)

3-23: Consider adding a display name for better debugging.

The component uses memo but lacks a displayName, which can make React DevTools harder to navigate.

Apply this diff:

 const TextPreview = memo(({ data }) => {
   const displayData = useMemo(() => {
     if (data === null || data === undefined) {
       return String(data);
     }
     if (typeof data === 'object') {
       try {
         return JSON.stringify(data);
       } catch {
         return String(data);
       }
     }
     return String(data);
   }, [data]);

   return (
     <div className="p-4 font-mono text-[13px] whitespace-pre-wrap break-words overflow-auto overflow-x-hidden w-full max-w-full h-full">
       {displayData}
     </div>
   );
 });

+TextPreview.displayName = 'TextPreview';
+
 export default TextPreview;
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/JsonPreview.js (2)

7-25: Extract helper function outside component for better performance.

The validateJsonData helper is recreated on every render. Since it doesn't depend on props or state, extracting it outside the component body will improve performance.

Apply this diff:

 import React from 'react';
 import ReactJson from 'react-json-view';
 import ErrorAlert from 'components/ErrorAlert/index';

+// Helper function to validate and parse JSON data
+const validateJsonData = (data) => {
+  // If data is already an object or array, use it directly
+  if (typeof data === 'object' && data !== null) {
+    return { data, error: null };
+  }
+
+  // If data is a string, try to parse it
+  if (typeof data === 'string') {
+    try {
+      const parsed = JSON.parse(data);
+      return { data: parsed, error: null };
+    } catch (e) {
+      return { data: null, error: `Invalid JSON format: ${e.message}` };
+    }
+  }
+
+  // For other types, return error
+  return { data: null, error: 'Invalid input. Expected a JSON object, array, or valid JSON string.' };
+};
+
 const JsonPreview = ({ data, displayedTheme }) => {
-  // Helper function to validate and parse JSON data
-  const validateJsonData = (data) => {
-    // If data is already an object or array, use it directly
-    if (typeof data === 'object' && data !== null) {
-      return { data, error: null };
-    }
-
-    // If data is a string, try to parse it
-    if (typeof data === 'string') {
-      try {
-        const parsed = JSON.parse(data);
-        return { data: parsed, error: null };
-      } catch (e) {
-        return { data: null, error: `Invalid JSON format: ${e.message}` };
-      }
-    }
-
-    // For other types, return error
-    return { data: null, error: 'Invalid input. Expected a JSON object, array, or valid JSON string.' };
-  };
-
   // Validate and parse JSON data
   const jsonData = validateJsonData(data);

5-61: Consider adding a display name for better debugging.

Adding a displayName will make this component easier to identify in React DevTools.

Add after the component definition:

 const JsonPreview = ({ data, displayedTheme }) => {
   // ... component implementation
 };

+JsonPreview.displayName = 'JsonPreview';
+
 export default JsonPreview;
packages/bruno-app/src/components/ErrorAlert/index.js (2)

13-13: Add error handling for JSON.stringify to prevent crashes.

JSON.stringify will throw if message contains circular references. Since this component handles error display, it should be resilient to malformed input.

Apply this diff:

-          <div className="error-message">{typeof message === 'string' ? message : JSON.stringify(message, null, 2)}</div>
+          <div className="error-message">
+            {typeof message === 'string' ? message : (() => {
+              try {
+                return JSON.stringify(message, null, 2);
+              } catch {
+                return String(message);
+              }
+            })()}
+          </div>

9-9: Consider making margins configurable via props.

Hardcoded margin classes (mt-4 mb-2) reduce reusability. Consider accepting a className prop to allow customization when needed.

Example implementation:

-const ErrorAlert = ({ title, message, onClose }) => {
+const ErrorAlert = ({ title, message, onClose, className = 'mt-4 mb-2' }) => {
   if (!message) return null;

   return (
-    <StyledWrapper className="mt-4 mb-2">
+    <StyledWrapper className={className}>
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/VideoPreview.js (1)

1-3: Consolidate React imports.

Minor cleanup: merge the two React import statements.

-import React from 'react';
-import { useEffect, useState } from 'react';
+import React, { useEffect, useState } from 'react';
packages/bruno-app/src/components/ErrorAlert/StyledWrapper.js (1)

13-13: Fragile dark theme detection.

Comparing against a hardcoded hex value ('#1e1e1e') is brittle. If the theme's background color changes, this condition breaks.

Consider using a dedicated theme property if available (e.g., props.theme.mode === 'dark') or define the background colors in the theme itself.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview.js (2)

27-32: Move isValidTreeData outside the component.

This function is recreated on every render. Move it to module scope since it doesn't depend on component state or props.

+const isValidTreeData = (data) => {
+  if (data === null || data === undefined) return false;
+  if (typeof data === 'object' && !Array.isArray(data)) return true;
+  if (Array.isArray(data)) return true;
+  return false;
+};
+
 export default function XmlPreview({ data, defaultExpanded = true }) {
   // ...
-  const isValidTreeData = (data) => {
-    if (data === null || data === undefined) return false;
-    if (typeof data === 'object' && !Array.isArray(data)) return true;
-    if (Array.isArray(data)) return true;
-    return false;
-  };

175-177: Remove unused variables.

hasArrayValue and arrayLength are declared but never used. Array nodes are handled earlier at lines 82-99.

-  // Determine if this node's value is an array
-  const hasArrayValue = Array.isArray(node);
-  const arrayLength = hasArrayValue ? node.length : 0;
packages/bruno-app/src/components/ButtonDropdown/index.jsx (2)

20-20: Unused dropdownTippyRef - remove or utilize.

The ref is assigned but never read. If intended for programmatic dropdown control (e.g., closing on selection), implement that; otherwise, remove the dead code.

Also applies to: 42-44


46-48: Consider inlining handleOptionSelect.

This wrapper only calls onChange without additional logic. Per coding guidelines, avoid single-line abstractions that only increase the call stack.

-  const handleOptionSelect = (optionValue) => {
-    onChange(optionValue);
-  };
...
-                onClick={() => handleOptionSelect(option.value)}
+                onClick={() => onChange(option.value)}
packages/bruno-app/src/utils/response/index.js (1)

213-218: MP4 detection may miss some valid files.

The MP4/MOV ftyp box can have sizes other than 0x18 or 0x20. Consider checking just for the ftyp signature at bytes 4-7 regardless of box size.

-  if (bytes[0] === 0x00 && bytes[1] === 0x00 && bytes[2] === 0x00
-    && (bytes[3] === 0x18 || bytes[3] === 0x20)
-    && bytes[4] === 0x66 && bytes[5] === 0x74 && bytes[6] === 0x79 && bytes[7] === 0x70) {
+  // ftyp box - check signature at offset 4 regardless of box size
+  if (bytes[4] === 0x66 && bytes[5] === 0x74 && bytes[6] === 0x79 && bytes[7] === 0x70) {
     return 'video/mp4';
   }
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (1)

1-1: useMemo is imported but not used.

Remove unused import to keep the codebase clean.

-import React, { useState, useMemo } from 'react';
+import React, { useState } from 'react';
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (1)

117-125: Unused responseEncoding in dependency array.

responseEncoding is listed as a dependency but not passed to formatResponse. Either remove from deps or pass it to the function if needed.

📜 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 dd72ee5 and 4bdd37d.

📒 Files selected for processing (25)
  • packages/bruno-app/src/components/ButtonDropdown/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ButtonDropdown/index.jsx (1 hunks)
  • packages/bruno-app/src/components/ErrorAlert/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ErrorAlert/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/JsonPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/TextPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/VideoPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (4 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (4 hunks)
  • packages/bruno-app/src/utils/common/codemirror.js (1 hunks)
  • packages/bruno-app/src/utils/common/index.js (4 hunks)
  • packages/bruno-app/src/utils/response/index.js (1 hunks)
  • tests/response/large-response-crash-prevention.spec.ts (1 hunks)
  • tests/response/response-format-select-and-preview/fixtures/collection/bruno.json (1 hunks)
  • tests/response/response-format-select-and-preview/fixtures/collection/request-html.bru (1 hunks)
  • tests/response/response-format-select-and-preview/fixtures/collection/request-json.bru (1 hunks)
  • tests/response/response-format-select-and-preview/init-user-data/collection-security.json (1 hunks)
  • tests/response/response-format-select-and-preview/init-user-data/preferences.json (1 hunks)
  • tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts (1 hunks)
  • tests/utils/page/actions.ts (2 hunks)
  • tests/utils/page/locators.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. No tabs, just spaces
Stick to single quotes for strings
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single parameters
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses. Keep 'em tight
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
Avoid single line abstractions where all that's being done is increasing the call stack with one additional function
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-app/src/components/ButtonDropdown/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/TextPreview.js
  • tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx
  • packages/bruno-app/src/components/ErrorAlert/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/JsonPreview.js
  • packages/bruno-app/src/components/ButtonDropdown/index.jsx
  • tests/utils/page/locators.ts
  • tests/response/large-response-crash-prevention.spec.ts
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/VideoPreview.js
  • packages/bruno-app/src/utils/response/index.js
  • packages/bruno-app/src/utils/common/index.js
  • packages/bruno-app/src/utils/common/codemirror.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview.js
  • packages/bruno-app/src/components/ErrorAlert/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js
  • tests/utils/page/actions.ts
tests/**/**.*

⚙️ CodeRabbit configuration file

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

  • Follow best practices for Playwright code and e2e automation

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

  • Avoid using page.pause() in code

  • Use locator variables for locators

  • Avoid using test.only

  • Use multiple assertions

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

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

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

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

Files:

  • tests/response/response-format-select-and-preview/init-user-data/collection-security.json
  • tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts
  • tests/response/response-format-select-and-preview/fixtures/collection/request-html.bru
  • tests/utils/page/locators.ts
  • tests/response/large-response-crash-prevention.spec.ts
  • tests/response/response-format-select-and-preview/fixtures/collection/bruno.json
  • tests/response/response-format-select-and-preview/init-user-data/preferences.json
  • tests/response/response-format-select-and-preview/fixtures/collection/request-json.bru
  • tests/utils/page/actions.ts
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

Multiple expressions per line in JSX are fine – flexibility is nice

Files:

  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx
  • packages/bruno-app/src/components/ButtonDropdown/index.jsx
🧬 Code graph analysis (8)
packages/bruno-app/src/components/ButtonDropdown/StyledWrapper.js (1)
packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js (1)
  • StyledWrapper (3-59)
tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts (3)
tests/utils/page/actions.ts (6)
  • closeAllCollections (205-205)
  • openRequest (214-214)
  • sendRequestAndWaitForResponse (210-210)
  • switchToPreviewTab (212-212)
  • switchToEditorTab (213-213)
  • switchResponseFormat (211-211)
tests/utils/page/locators.ts (1)
  • buildCommonLocators (3-65)
packages/bruno-app/src/utils/codemirror/brunoVarInfo.js (1)
  • editorContainer (298-298)
packages/bruno-app/src/components/ErrorAlert/index.js (1)
packages/bruno-app/src/components/ErrorAlert/StyledWrapper.js (1)
  • StyledWrapper (3-42)
packages/bruno-app/src/components/ButtonDropdown/index.jsx (3)
packages/bruno-app/src/components/Icons/IconCaretDown/index.js (1)
  • IconCaretDown (3-16)
packages/bruno-app/src/components/ButtonDropdown/StyledWrapper.js (1)
  • StyledWrapper (3-7)
packages/bruno-app/src/components/Dropdown/index.js (1)
  • Dropdown (5-25)
packages/bruno-app/src/utils/response/index.js (3)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (1)
  • contentType (56-56)
packages/bruno-app/src/utils/common/index.js (3)
  • contentType (94-94)
  • i (21-21)
  • i (498-498)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview.js (1)
  • parser (287-287)
packages/bruno-app/src/utils/common/codemirror.js (2)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (1)
  • contentType (56-56)
packages/bruno-app/src/utils/common/index.js (1)
  • contentType (94-94)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview.js (3)
packages/bruno-app/src/utils/common/index.js (3)
  • parsed (320-320)
  • i (21-21)
  • i (498-498)
packages/bruno-app/src/components/ErrorAlert/index.js (1)
  • ErrorAlert (5-23)
packages/bruno-app/src/utils/response/index.js (2)
  • parser (156-156)
  • i (79-79)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (3)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (1)
  • HtmlPreview (5-39)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/JsonPreview.js (1)
  • JsonPreview (5-61)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview.js (1)
  • XmlPreview (5-68)
🔇 Additional comments (30)
packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js (1)

6-6: LGTM!

The row height increase to 1.75rem properly accommodates the new response format selector dropdown, maintaining adequate vertical space for the enhanced preview UI.

tests/response/large-response-crash-prevention.spec.ts (1)

41-41: LGTM!

Good addition of exact: true - prevents false matches with other buttons like "View Raw" that might contain "View" in the name.

tests/response/response-format-select-and-preview/fixtures/collection/request-html.bru (1)

1-23: LGTM!

Fixture correctly defines an HTML echo request for testing the HTML preview functionality. The timeout: 0 (no timeout) is acceptable for test fixtures where external service latency may vary.

Note: This test depends on httpfaker.org - ensure this external service is reliable for CI/CD pipelines.

tests/response/response-format-select-and-preview/init-user-data/preferences.json (1)

1-6: LGTM!

Properly structured test initialization data with the {{projectRoot}} placeholder for portable collection path resolution.

packages/bruno-app/src/utils/common/index.js (3)

7-8: LGTM on prettier integration.

Using prettier/standalone with parser-babel is the correct approach for browser/Electron environments where the full prettier package isn't available.


106-117: LGTM on JavaScript content-type detection.

The pattern correctly matches common JavaScript MIME types (application/javascript, text/javascript, application/ecmascript, text/ecmascript).


494-511: LGTM on hex view formatting.

Clean implementation with proper offset display, hex bytes, and ASCII representation. The 16-byte width is standard for hex dumps.

tests/response/response-format-select-and-preview/init-user-data/collection-security.json (1)

1-10: LGTM!

The test configuration is correctly structured with appropriate sandbox settings for testing response format features.

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

1-9: LGTM!

Clean styled component wrapper with appropriate SVG color inheritance for the caret icon.

tests/response/response-format-select-and-preview/fixtures/collection/request-json.bru (1)

1-17: LGTM!

Well-formed Bruno request fixture using the echo endpoint to test JSON response handling.

packages/bruno-app/src/utils/common/codemirror.js (1)

106-107: LGTM!

Correctly adds JavaScript content-type detection following the established pattern. The check for both 'javascript' and 'ecmascript' ensures comprehensive coverage of JS MIME types.

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

54-64: LGTM!

Well-structured locator additions that follow the existing patterns and conventions. The use of both test IDs and CSS selectors is appropriate for the different element types.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (1)

5-18: LGTM on the HTML preview logic.

The webview injection with base URL and the use of escapeHtml for sanitization is well implemented. The dual validation using isValidHtml and isValidHtmlSnippet properly handles both full HTML documents and snippets.

tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts (3)

1-16: Well-structured test suite following Playwright best practices.

Good use of test.step for report readability, locator variables for maintainability, and multiple assertions per step. The serial execution and cleanup in afterAll is appropriate for this stateful UI flow.


18-88: Comprehensive JSON response format coverage.

The test covers all format options (JSON, HTML, XML, JavaScript, Raw, Hex, Base64) with both editor and preview verification. The error state assertions (e.g., "Cannot preview as XML" for JSON data) properly validate fallback behavior.


90-160: Thorough HTML response format test.

Validates the webview visibility for HTML preview and error states for incompatible formats (e.g., "Cannot preview as JSON"). The Hex and Base64 content assertions provide good regression coverage. Ensure fixture files exist in the expected location at tests/response/response-format-select-and-preview/fixtures and verify the test follows all Playwright best practices including proper use of test.step, locator variables, and absence of page.waitForTimeout(), page.pause(), or test.only.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/VideoPreview.js (1)

8-15: Solid Blob URL lifecycle management.

Proper cleanup via URL.revokeObjectURL in the useEffect return prevents memory leaks. The dependency array correctly includes both contentType and dataBuffer.

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

28-41: Clean error styling with theme awareness.

The error-title and error-message styles appropriately use theme tokens for colors and font sizing.

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

136-144: Clean implementation of openRequest helper.

Properly scopes the request locator within the collection context.


152-163: Well-designed sendRequestAndWaitForResponse with configurable options.

The default timeout of 15000ms and options object pattern is clean. Good use of test.step for reporting.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview.js (1)

282-362: XML parsing implementation is solid.

The parseXMLString function handles attributes, text nodes, and repeated elements correctly. Error detection via parsererror check is appropriate.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx (1)

6-39: Clean component structure with good UX patterns.

The ToggleSwitch header with conditional title and the IconEye suffix for preview mode provide clear visual feedback. Props are well-named.

Verify the "active" class on line 35 is actually styled, as it may be unused:

className="active mr-[2px]"

Search for .active class definitions in CSS/SCSS files in the ResponsePane and ButtonDropdown component directories.

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

1-59: LGTM!

Clean rule-based MIME type matching with sensible fallback. The pattern is extensible for future content types.


61-70: LGTM!

Standard HTML entity escaping implementation - covers the essential characters for XSS prevention.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (2)

64-80: LGTM!

Clean separation of the editor rendering path with proper prop forwarding.


121-132: LGTM!

Good fallback UX for unsupported content types instead of failing silently.

packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (4)

14-32: LGTM!

Well-structured format options constant with appropriate CodeMirror modes.


48-56: LGTM!

Proper error handling for buffer conversion with fallback to null.


66-86: LGTM!

Sensible restriction of format options for binary content types.


213-227: LGTM!

Clean integration of the new preview system with proper prop forwarding to QueryResultPreview.

Comment on lines +99 to +133
const ButtonIcon = forwardRef((iconProps, ref) => {
return (
<button
ref={ref}
className={classnames('flex items-center gap-1.5 text-xs',
'cursor-pointer select-none',
'h-7 rounded-[6px] border px-2 transition-colors',
// Text color
'text-gray-700 dark:text-gray-300',
// Border color
'border-gray-300 dark:border-[#343434]',
// Background color
// Hover background color
'hover:bg-gray-200 dark:hover:bg-[#303030]',
{
// Disabled styles
'opacity-50 cursor-not-allowed': disabled
},
className)}
data-testid={props['data-testid']}
style={style}
role="button"
{...iconProps}
>
{prefix && (
<span>{prefix}</span>
)}
<span className="active">{selectedLabel}</span>
{suffix && (
<span>{suffix}</span>
)}
<IconCaretDown className="caret ml-1" size={14} strokeWidth={2} />
</button>
);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Move ButtonIcon outside the component or memoize it.

Defining a forwardRef component inside the render function causes React to recreate it on every render, losing internal state and causing unnecessary DOM updates.

+const ButtonIcon = forwardRef(({ disabled, className, style, prefix, selectedLabel, suffix, ...props }, ref) => {
+  return (
+    <button
+      ref={ref}
+      className={classnames('flex items-center gap-1.5 text-xs',
+        'cursor-pointer select-none',
+        'h-7 rounded-[6px] border px-2 transition-colors',
+        'text-gray-700 dark:text-gray-300',
+        'border-gray-300 dark:border-[#343434]',
+        'hover:bg-gray-200 dark:hover:bg-[#303030]',
+        { 'opacity-50 cursor-not-allowed': disabled },
+        className)}
+      data-testid={props['data-testid']}
+      style={style}
+      role="button"
+      {...props}
+    >
+      {prefix && <span>{prefix}</span>}
+      <span className="active">{selectedLabel}</span>
+      {suffix && <span>{suffix}</span>}
+      <IconCaretDown className="caret ml-1" size={14} strokeWidth={2} />
+    </button>
+  );
+});
+ButtonIcon.displayName = 'ButtonIcon';
+
 const ButtonDropdown = ({ ... }) => {
   // ...
-  const ButtonIcon = forwardRef((iconProps, ref) => { ... });
-  ButtonIcon.displayName = 'ButtonIcon';

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

🤖 Prompt for AI Agents
In packages/bruno-app/src/components/ButtonDropdown/index.jsx around lines 99 to
133, ButtonIcon is defined inline with forwardRef inside the render which causes
it to be recreated on every render; move the forwardRef component definition out
of the parent component (to module scope) or memoize it with React.memo so it is
stable between renders, ensure it receives needed props (disabled, className,
style, prefix, suffix, selectedLabel, props/data-testid) via parameters rather
than closing over local variables, and update any refs/prop forwarding
accordingly so behavior and styling remain identical.

Comment on lines +101 to +121
<button
ref={ref}
className={classnames('flex items-center gap-1.5 text-xs',
'cursor-pointer select-none',
'h-7 rounded-[6px] border px-2 transition-colors',
// Text color
'text-gray-700 dark:text-gray-300',
// Border color
'border-gray-300 dark:border-[#343434]',
// Background color
// Hover background color
'hover:bg-gray-200 dark:hover:bg-[#303030]',
{
// Disabled styles
'opacity-50 cursor-not-allowed': disabled
},
className)}
data-testid={props['data-testid']}
style={style}
role="button"
{...iconProps}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add disabled attribute to the button for accessibility.

The button has visual disabled styling but lacks the actual disabled attribute, which means keyboard/screen reader users can still interact with it.

       <button
         ref={ref}
         className={classnames(...)}
         data-testid={props['data-testid']}
         style={style}
         role="button"
+        disabled={disabled}
         {...iconProps}
       >
🤖 Prompt for AI Agents
In packages/bruno-app/src/components/ButtonDropdown/index.jsx around lines
101-121, the <button> uses visual disabled styles but lacks the actual disabled
attribute so assistive tech and keyboard users can still interact; update the
element to include disabled={disabled} and also set tabIndex={disabled ? -1 :
undefined} (and optionally aria-disabled={disabled}) so the button is
non-interactive to screen readers and keyboard when disabled while preserving
semantics.

Comment on lines +24 to +25
} else if (typeof data === 'object') {
displayContent = JSON.stringify(data, null);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing indentation argument in JSON.stringify.

The comment states "render safely as formatted text", but objects won't be prettified without the third argument.

-  } else if (typeof data === 'object') {
-    displayContent = JSON.stringify(data, null);
+  } else if (typeof data === 'object') {
+    displayContent = JSON.stringify(data, null, 2);
📝 Committable suggestion

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

Suggested change
} else if (typeof data === 'object') {
displayContent = JSON.stringify(data, null);
} else if (typeof data === 'object') {
displayContent = JSON.stringify(data, null, 2);
🤖 Prompt for AI Agents
In
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js
around lines 24-25, the code uses JSON.stringify(data, null) which omits the
indentation argument so objects are not prettified; update the call to include a
spacing argument (for example JSON.stringify(data, null, 2)) so the object is
rendered as formatted text and remains safe for display.

Comment on lines +154 to +168
try {
// Parse with DOMParser
const parser = new DOMParser();
const doc = parser.parseFromString(trimmed, 'text/html');

// Check for parsing errors
const parseError = doc.querySelector('parsererror');
if (parseError) {
return false;
}

return true;
} catch (error) {
return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

DOMParser with 'text/html' never produces parsererror.

The HTML parser is lenient and won't generate a parsererror element - that behavior is specific to XML parsing. The try-catch and parsererror check are effectively dead code here. Consider removing them or clarifying the intent.

   try {
-    // Parse with DOMParser
     const parser = new DOMParser();
     const doc = parser.parseFromString(trimmed, 'text/html');
-
-    // Check for parsing errors
-    const parseError = doc.querySelector('parsererror');
-    if (parseError) {
-      return false;
-    }
-
+    // HTML parser is lenient; if we reach here with valid tags, consider it valid
     return true;
   } catch (error) {
     return false;
   }
🤖 Prompt for AI Agents
In packages/bruno-app/src/utils/response/index.js around lines 154-168, the
DOMParser parseFromString(..., 'text/html') branch will never produce a
'parsererror' node and the try-catch/parseError check is therefore dead; remove
the parsererror check and unnecessary try-catch, and either (a) simply return
true after parsing since the HTML parser is lenient, or (b) if strict validation
is required, switch to parsing as XML (e.g., 'application/xml') and
handle/document parseError reliably or use a proper HTML validation library
(htmlparser2/validator) to check for well-formedness — implement one of these
fixes and update comments accordingly.

Comment on lines +191 to +202
/**
* Switch to the editor tab
* @param page - The page object
*/
const switchToEditorTab = async (page: Page) => {
await test.step('Switch to editor tab', async () => {
const responseFormatTab = page.getByTestId('format-response-tab');
await responseFormatTab.click();
const previewTab = page.getByTestId('preview-response-tab');
await previewTab.click();
});
};
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

Copy-paste bug: switchToEditorTab is identical to switchToPreviewTab.

Both functions click the same preview-response-tab toggle. While this might accidentally work (toggling the switch), the implementation is misleading. If the preview is already off, switchToEditorTab would incorrectly turn it on.

 const switchToEditorTab = async (page: Page) => {
   await test.step('Switch to editor tab', async () => {
     const responseFormatTab = page.getByTestId('format-response-tab');
     await responseFormatTab.click();
     const previewTab = page.getByTestId('preview-response-tab');
-    await previewTab.click();
+    // Only toggle off if preview is currently on
+    const isPreviewOn = await previewTab.isChecked();
+    if (isPreviewOn) {
+      await previewTab.click();
+    }
   });
 };

Alternatively, if the toggle state is managed differently, verify the intended behavior matches the implementation.

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

🤖 Prompt for AI Agents
In tests/utils/page/actions.ts around lines 191 to 202, the switchToEditorTab
function mistakenly clicks the preview tab (copy-paste from switchToPreviewTab);
change the second click to the editor tab test id (e.g., 'editor-response-tab')
so it explicitly selects the editor view, and verify the test-id name matches
the app implementation; keep the outer test.step and await calls intact.

@@ -0,0 +1,159 @@
import React, { useRef, forwardRef } from 'react';
Copy link
Contributor

Choose a reason for hiding this comment

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

Please move generic components to ui folder

'cursor-pointer select-none',
'h-7 rounded-[6px] border px-2 transition-colors',
// Text color
'text-gray-700 dark:text-gray-300',
Copy link
Contributor

@helloanoop helloanoop Dec 2, 2025

Choose a reason for hiding this comment

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

No tailwind class colors please

border-top: 1px solid transparent;
border-right: 1px solid transparent;
border-bottom: 1px solid transparent;
border-radius: 0.375rem;
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use base radius from theme

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (1)

53-63: Pretty‑print objects in the text fallback (same as earlier review)

The comment says “render safely as formatted text”, but JSON.stringify(data, null) won’t pretty‑print objects; it produces a single‑line JSON string.

You can pass a spacing argument to improve readability:

-  } else if (typeof data === 'object') {
-    displayContent = JSON.stringify(data, null);
+  } else if (typeof data === 'object') {
+    displayContent = JSON.stringify(data, null, 2);

This matches the intent of a “formatted” safe text fallback without changing behavior for other types.

🧹 Nitpick comments (2)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (2)

10-28: Tighten MutationObserver usage and avoid redundant state updates

The drag detection works, but observing watchTarget with subtree: true plus unguarded setIsDragging can cause a lot of state churn when classes change frequently.

Consider:

  • Deduping updates so React state only changes when the value actually flips.
  • Running an initial checkDragging() so isDragging is correct even before the first mutation.

For example:

  useEffect(() => {
    if (!webviewContainerRef.current) return;

    const checkDragging = () => {
-      const hasDraggingParent = webviewContainerRef.current?.closest('.dragging');
-      setIsDragging(!!hasDraggingParent);
+      const hasDraggingParent = !!webviewContainerRef.current?.closest('.dragging');
+      setIsDragging(prev => (prev === hasDraggingParent ? prev : hasDraggingParent));
    };

    const watchTarget = webviewContainerRef.current.closest('[class*="flex"]') || document.body;

    const mutationObserver = new MutationObserver(checkDragging);
    mutationObserver.observe(watchTarget, {
      attributes: true,
      attributeFilter: ['class'],
      subtree: true
    });

+    checkDragging();
+
    return () => mutationObserver.disconnect();
  }, []);

If feasible, also narrowing watchTarget to the smallest container that actually toggles the .dragging class would help keep observer load down.


30-36: Guard HTML detection to string data before running validators

Right now isValidHtml(data) || isValidHtmlSnippet(data) is evaluated for any data type. If those utilities are written for strings, it’s safer and a bit cheaper to short‑circuit non‑string values before running HTML heuristics and calling .includes:

-  if (isValidHtml(data) || isValidHtmlSnippet(data)) {
+  if (typeof data === 'string' && (isValidHtml(data) || isValidHtmlSnippet(data))) {

This makes the intent explicit and avoids surprising behavior if non‑string data is ever passed in.

📜 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 4bdd37d and e3842ec.

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

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single parameters
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses. Keep 'em tight
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
Avoid single line abstractions where all that's being done is increasing the call stack with one additional function
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js
⏰ 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: Unit Tests
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: SSL Tests - Linux

@abhishek-bruno abhishek-bruno force-pushed the feat/response-data-type-selector branch from e3842ec to 88ce6eb Compare December 4, 2025 05:36
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

8-8: Use shared theme border radius instead of literal rem

border-radius: 0.375rem; should likely use the base radius from the theme (whatever the canonical token is here) for consistency with the rest of the design system, instead of an inline rem value. Based on learnings, surface radii should come from theme tokens.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (1)

57-58: Add indentation argument to JSON.stringify for readable output.

The fallback text rendering stringifies objects without indentation, making complex objects harder to read.

   } else if (typeof data === 'object') {
-    displayContent = JSON.stringify(data, null);
+    displayContent = JSON.stringify(data, null, 2);
   }
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview.js (1)

228-262: DOM manipulation bypasses React reconciliation.

The array collapse/expand logic uses direct DOM queries (parentElement.querySelector, classList.toggle) instead of React state. Extract this into a separate component with its own useState for expansion control, consistent with how XmlNode handles expansion.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (1)

87-88: Add alt attribute for accessibility.

The image element is missing an alt attribute, which is required for screen readers.

-      return <img src={`data:${contentType.replace(/\;(.*)/, '')};base64,${dataBuffer}`} />;
+      return <img src={`data:${contentType.replace(/\;(.*)/, '')};base64,${dataBuffer}`} alt="Response preview" />;
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (3)

107-115: Condition detectedContentType !== undefined is always true.

detectContentTypeFromBuffer returns string | null, never undefined. The check should be !== null or simplified.

   useEffect(() => {
-    if (isInitialRun && (detectedContentType !== undefined && contentType !== undefined)) {
+    if (isInitialRun && contentType !== undefined) {
       const initial = getDefaultResponseFormat(contentType);
       setSelectedFormat(initial.format);
       setSelectedTab(initial.tab);
       setIsInitialRun(false);
     }
-  }, [contentType, isInitialRun, detectedContentType]);
+  }, [contentType, isInitialRun]);

131-150: Add fallback return and fix dependencies.

The useMemo can implicitly return undefined if selectedFormat is unexpected. Also, contentType is in the dependency array but detectedContentType is what's actually used.

       // for all other content types, return preview-text
       return 'preview-text';
     }
-  }, [selectedFormat, contentType, dataBuffer]);
+    return 'preview-text'; // fallback for unexpected formats
+  }, [selectedFormat, detectedContentType]);

168-178: Missing dependencies in useMemo.

previewFormatOptions, handleFormatChange, and onPreviewTabSelect are referenced but not in the dependency array, which can cause stale closures.

-  }, [selectedFormat, selectedTab]);
+  }, [selectedFormat, selectedTab, previewFormatOptions, handleFormatChange, onPreviewTabSelect]);

Alternatively, wrap handleFormatChange and onPreviewTabSelect in useCallback to stabilize their references.

🧹 Nitpick comments (6)
packages/bruno-app/src/components/ErrorAlert/StyledWrapper.js (2)

4-13: Avoid hard‑coded theme.bg comparison for background color

Branching on props.theme.bg === '#1e1e1e' is brittle and couples this component to a specific dark theme implementation. Prefer a dedicated theme token (e.g. per‑mode background or an errorAlert surface color) so future theme changes don’t require touching this component. Based on learnings, theme props should encapsulate color decisions rather than magic hex checks.


34-39: Revisit word-break: break-all for error message readability

word-break: break-all will split words arbitrarily, which can hurt readability for typical error text. If the primary concern is long tokens (e.g. stack traces, UUIDs), consider overflow-wrap: anywhere or word-break: break-word to keep long segments wrapping without aggressively breaking normal words.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (1)

43-48: Consider hardening webview security preferences.

Explicitly disabling Node integration adds defense in depth for rendering untrusted HTML content.

         <webview
           src={`data:text/html; charset=utf-8,${encodeURIComponent(htmlContent)}`}
-          webpreferences="disableDialogs=true, javascript=yes"
+          webpreferences="disableDialogs=true, javascript=yes, nodeIntegration=no, contextIsolation=yes"
           className="h-full bg-white"
           style={dragStyles}
         />
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview.js (1)

27-32: Move helper function outside component body.

isValidTreeData is redefined on every render. Extract it to module scope since it has no dependencies on component state or props.

+const isValidTreeData = (data) => {
+  if (data === null || data === undefined) return false;
+  if (typeof data === 'object' && !Array.isArray(data)) return true;
+  if (Array.isArray(data)) return true;
+  return false;
+};
+
 export default function XmlPreview({ data, defaultExpanded = true }) {
   // Parse XML string
   const parsedData = useMemo(() => {
     // ...
   }, [data]);

   // Check for parsing error
   if (parsedData && typeof parsedData === 'object' && parsedData.error) {
     // ...
   }

-  // Validate that data can be rendered as a tree
-  const isValidTreeData = (data) => {
-    if (data === null || data === undefined) return false;
-    if (typeof data === 'object' && !Array.isArray(data)) return true;
-    if (Array.isArray(data)) return true;
-    return false;
-  };
-
   if (!isValidTreeData(parsedData)) {
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (1)

1-1: Remove unused import.

useMemo is imported but not used in this file.

-import React, { useState, useMemo } from 'react';
+import React, { useState } from 'react';
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (1)

117-125: Remove unused dependency responseEncoding.

responseEncoding is in the dependency array but not used in the memoized function.

-    [data, dataBuffer, responseEncoding, selectedFormat, filter, isLargeResponse, showLargeResponse]
+    [data, dataBuffer, selectedFormat, filter, isLargeResponse, showLargeResponse]
📜 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 e3842ec and 88ce6eb.

📒 Files selected for processing (13)
  • packages/bruno-app/src/components/ButtonDropdown/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ButtonDropdown/index.jsx (1 hunks)
  • packages/bruno-app/src/components/ErrorAlert/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ErrorAlert/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/JsonPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/TextPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/VideoPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (4 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (4 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/bruno-app/src/components/ButtonDropdown/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/JsonPreview.js
  • packages/bruno-app/src/components/ErrorAlert/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/VideoPreview.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, 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() not func ()
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/ErrorAlert/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/TextPreview.js
  • packages/bruno-app/src/components/ButtonDropdown/index.jsx
  • packages/bruno-app/src/components/ResponsePane/QueryResult/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{jsx,tsx}: JSX is enabled and should be used where it makes sense
Multiple expressions per line in JSX are acceptable for flexibility
In styled components and React components using styled components, use the theme prop to manage CSS colors instead of CSS variables
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
Styled Component CSS may change layout, but Tailwind classes should not define colors

Files:

  • packages/bruno-app/src/components/ButtonDropdown/index.jsx
🧠 Learnings (3)
📚 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/ErrorAlert/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/ErrorAlert/StyledWrapper.js
  • packages/bruno-app/src/components/ButtonDropdown/index.jsx
📚 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} : In styled components and React components using styled components, use the theme prop to manage CSS colors instead of CSS variables

Applied to files:

  • packages/bruno-app/src/components/ErrorAlert/StyledWrapper.js
🧬 Code graph analysis (4)
packages/bruno-app/src/components/ButtonDropdown/index.jsx (3)
packages/bruno-app/src/components/Icons/IconCaretDown/index.js (1)
  • IconCaretDown (3-16)
packages/bruno-app/src/components/ButtonDropdown/StyledWrapper.js (1)
  • StyledWrapper (3-7)
packages/bruno-app/src/components/Dropdown/index.js (1)
  • Dropdown (5-25)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (5)
packages/bruno-app/src/utils/common/index.js (7)
  • contentType (93-93)
  • getContentType (83-119)
  • getContentType (83-119)
  • getEncoding (191-195)
  • getEncoding (191-195)
  • formatResponse (273-429)
  • formatResponse (273-429)
packages/bruno-app/src/utils/response/index.js (4)
  • detectContentTypeFromBuffer (176-258)
  • detectContentTypeFromBuffer (176-258)
  • getDefaultResponseFormat (8-59)
  • getDefaultResponseFormat (8-59)
packages/bruno-app/src/providers/Theme/index.js (3)
  • useTheme (61-69)
  • useTheme (61-69)
  • displayedTheme (11-11)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (1)
  • QueryResultPreview (20-133)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx (1)
  • QueryResultTypeSelector (6-39)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview.js (3)
packages/bruno-app/src/utils/common/index.js (3)
  • parsed (318-318)
  • i (21-21)
  • i (496-496)
packages/bruno-app/src/components/ErrorAlert/index.js (1)
  • ErrorAlert (5-23)
packages/bruno-app/src/utils/response/index.js (2)
  • parser (156-156)
  • i (79-79)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (4)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (1)
  • HtmlPreview (5-72)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/JsonPreview.js (1)
  • JsonPreview (5-61)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/TextPreview.js (1)
  • TextPreview (3-23)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview.js (1)
  • XmlPreview (5-68)
⏰ 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 - Windows
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: SSL Tests - macOS
🔇 Additional comments (5)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/TextPreview.js (1)

1-25: LGTM!

Clean implementation. The memoization strategy is appropriate and the fallback handling for different data types is solid.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview.js (1)

86-98: Index keys acceptable for static parsed data.

Using array indices as keys is fine here since the parsed XML structure is immutable during the component's lifecycle.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (1)

109-119: LGTM!

Clean integration of the new preview components with appropriate mode switching.

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

48-55: LGTM!

Proper error handling for buffer conversion with graceful fallback.

packages/bruno-app/src/components/ButtonDropdown/index.jsx (1)

2-2: Verify IconCaretDown import source.

The import on line 2 references @tabler/icons, but the review suggests a local IconCaretDown component may exist at packages/bruno-app/src/components/Icons/IconCaretDown/index.js. Confirm whether the local component is available and whether it should be imported instead.

{header && (
<div className="dropdown-header-container">
{header}
<div className="h-px bg-[#e7e7e7] dark:bg-[#444] my-1"></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 | 🟡 Minor

Avoid Tailwind color classes per coding guidelines.

Line 149 uses Tailwind classes with hex color values (bg-[#e7e7e7] and dark:bg-[#444]). Per the coding guidelines, Tailwind classes should not define colors - use styled components with theme props instead.

As per coding guidelines, Tailwind classes should be used for layout only, not colors.

Move color definitions to StyledWrapper:

+const StyledWrapper = styled.div`
+  .caret {
+    fill: currentColor;
+  }
+  .dropdown-header-separator {
+    height: 1px;
+    background-color: ${(props) => props.theme.dropdown.separator};
+    margin: 0.25rem 0;
+  }
+`;

Then update the JSX:

-              <div className="h-px bg-[#e7e7e7] dark:bg-[#444] my-1"></div>
+              <div className="dropdown-header-separator"></div>

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

🤖 Prompt for AI Agents
packages/bruno-app/src/components/ButtonDropdown/index.jsx around line 149: the
divider uses Tailwind hex color classes (bg-[#e7e7e7] and dark:bg-[#444]) which
violates the guideline to keep Tailwind for layout only. Remove the color
classes from the JSX (leave layout classes like h-px and my-1) and move the
color rules into the component's StyledWrapper: add a selector for the divider
(e.g., .divider or a direct child selector) that sets background-color using
theme props (use the appropriate theme token such as theme.colors.separator /
theme.colors.surface and the dark theme equivalent) so the divider color comes
from styled-components/theme rather than Tailwind.

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: 3

Caution

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

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

8-15: Duplicate keys in classnames object cause unintended status code coloring

Object literals retain only the last value for duplicate keys. This means:

  • 1xx responses get no class (first 'text-ok' is overwritten)
  • 2xx responses get 'text-ok'
  • 3xx–4xx responses get no class (first two 'text-error' entries are overwritten)
  • 5xx responses get 'text-error'

Consolidate the ranges with unique keys:

  const getTabClassname = (status) => {
    return classnames({
-     'text-ok': status >= 100 && status < 200,
-     'text-ok': status >= 200 && status < 300,
-     'text-error': status >= 300 && status < 400,
-     'text-error': status >= 400 && status < 500,
-     'text-error': status >= 500 && status < 600
+     'text-ok': status >= 100 && status < 300,
+     'text-error': status >= 300 && status < 600
    });
  };
♻️ Duplicate comments (3)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (1)

57-67: Prettify object fallback with JSON.stringify indentation

Right now objects are rendered on a single line because JSON.stringify(data, null) omits the spacing argument. Adding an indentation level will better match the “formatted text” comment and keep output readable.

-  } else if (typeof data === 'object') {
-    displayContent = JSON.stringify(data, null);
+  } else if (typeof data === 'object') {
+    displayContent = JSON.stringify(data, null, 2);
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (2)

46-68: Condition checks undefined but function returns null.

detectContentTypeFromBuffer returns string | null, never undefined. The check on line 61 (detectedContentType === undefined) will never be true, making this condition ineffective.

-    if (detectedContentType === undefined || contentType === undefined) {
+    if (detectedContentType === null && contentType === undefined) {
       return { initialFormat: null, initialTab: null };
     }

Alternatively, if the intent is to wait for valid content type detection, consider !detectedContentType && !contentType.


165-184: Missing explicit return and incorrect dependency.

This useMemo can return undefined implicitly if no condition matches (though current logic covers all cases). Also, contentType is in the dependency array but detectedContentType is what's actually used for binary format detection.

       return 'preview-text';
     }
+    return 'preview-text'; // fallback for any unhandled case
-  }, [selectedFormat, contentType, dataBuffer]);
+  }, [selectedFormat, detectedContentType]);
🧹 Nitpick comments (17)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js (1)

1-13: Consider consolidating .active styling.

The .active class here duplicates the same styling rule in the parent QueryResult/StyledWrapper.js (line 34-36). If the selector component is always nested within QueryResult, the parent's .active rule should cascade. Evaluate if this duplication is necessary for scoping or can be removed.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (1)

34-38: Consider more robust <head> injection for complex HTML

data.includes('<head>') only catches the exact lowercase token with no attributes; documents with <head> variants (e.g. <head lang="en">, uppercase tags) will be treated as snippets and get a prepended <head>...</head>, which is a bit fragile. You could tighten this later with a case‑insensitive regex on <head[^>]*> if you see real‑world HTML where this matters.

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

3-5: Remove empty StyledWrapper or add styles.

An empty styled component adds no value and increases bundle size. If styling is planned for future iterations, add a TODO comment; otherwise, remove this wrapper and render the component directly.

Apply this diff if no styling is needed:

-import styled from 'styled-components';
-
-const StyledWrapper = styled.div`
-
-`;
-
-export default StyledWrapper;

Then update the consuming component (ResponsePaneActions/index.js) to remove the StyledWrapper import and usage.

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

19-27: Consider initializing state from hooks to prevent flicker.

The state is initialized to hardcoded defaults ('raw', 'editor') and updated via useEffect. If initialFormat/initialTab are computed asynchronously, the component renders with incorrect defaults first, potentially causing visual flicker.

Consider initializing state directly from the hooks:

-  const [selectedFormat, setSelectedFormat] = useState('raw');
-  const [selectedTab, setSelectedTab] = useState('editor');
-
-  useEffect(() => {
-    if (initialFormat !== null && initialTab !== null) {
-      setSelectedFormat(initialFormat);
-      setSelectedTab(initialTab);
-    }
-  }, [initialFormat, initialTab]);
+  const [selectedFormat, setSelectedFormat] = useState(initialFormat ?? 'raw');
+  const [selectedTab, setSelectedTab] = useState(initialTab ?? 'editor');

If initialFormat/initialTab update after mount, add the useEffect back to sync changes.

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

53-65: Consider adding aria-disabled for better accessibility.

The outer div acts as a button with role="button" and receives focus, but when !hasData, clicks and key presses are silently ignored. Screen readers may still announce the element as interactive.

Apply this diff to improve accessibility:

-    <div role="button" tabIndex={0} onClick={handleClick} title={!children ? 'Copy response to clipboard' : null} onKeyDown={handleKeyDown}>
+    <div 
+      role="button" 
+      tabIndex={hasData ? 0 : -1} 
+      onClick={handleClick} 
+      title={!children ? 'Copy response to clipboard' : null} 
+      onKeyDown={handleKeyDown}
+      aria-disabled={!hasData}
+    >

This prevents focus on the disabled state and properly announces the disabled status to assistive technologies.

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

17-26: Unused Promise return value.

saveResponseToFile returns a Promise, but callers (onClick, handleKeyDown) ignore it. Either remove the return statement or use async/await to handle potential errors consistently.

 const saveResponseToFile = () => {
   if (isDisabled) {
     return;
   }
-  return new Promise((resolve, reject) => {
-    ipcRenderer
-      .invoke('renderer:save-response-to-file', response, item?.requestSent?.url, item.pathname)
-      .then(resolve)
-      .catch((err) => {
-        toast.error(get(err, 'error.message') || 'Something went wrong!');
-        reject(err);
-      });
-  });
+  ipcRenderer
+    .invoke('renderer:save-response-to-file', response, item?.requestSent?.url, item.pathname)
+    .catch((err) => {
+      toast.error(get(err, 'error.message') || 'Something went wrong!');
+    });
 };

52-58: Nested interactive elements cause accessibility issues.

A <button> inside a <div role="button"> creates confusing semantics for assistive technologies. Since the outer <div> handles click/keyboard events, the inner button is redundant.

     {children ? children : (
       <StyledWrapper className="flex items-center">
-        <button className="p-1">
+        <span className="p-1">
           <IconDownload size={16} strokeWidth={2} />
-        </button>
+        </span>
       </StyledWrapper>
     )}
packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js (2)

27-31: Inconsistent keyboard event handling.

Unlike ResponseDownload and ResponseBookmark, this handler doesn't call e.preventDefault() for Enter/Space. This could cause unintended form submission if used in a form context.

 const handleKeyDown = (e) => {
   if (e.key === 'Enter' || e.key === ' ') {
+    e.preventDefault();
     clearResponse();
   }
 };

35-41: Nested interactive elements cause accessibility issues.

Same issue as ResponseDownload — a <button> inside a <div role="button"> is semantically redundant and confusing for assistive technologies.

     {children ? children : (
       <StyledWrapper className="flex items-center">
-        <button className="p-1">
+        <span className="p-1">
           <IconEraser size={16} strokeWidth={2} />
-        </button>
+        </span>
       </StyledWrapper>
     )}
packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js (3)

53-72: Redundant disabled check.

The isDisabled check at lines 68-72 is unnecessary — isDisabled is derived from isResponseTooLarge || isStreamingResponse, and you've already handled isResponseTooLarge above. The streaming case is unreachable here since isDisabled would have been caught by click handler being skipped.

   if (isResponseTooLarge) {
     toast.error('Response size exceeds 5MB limit. Cannot save as example.');
     e.preventDefault();
     e.stopPropagation();
     return;
   }

-  if (isDisabled) {
-    e.preventDefault();
-    e.stopPropagation();
-    return;
-  }
+  if (isStreamingResponse) {
+    e.preventDefault();
+    e.stopPropagation();
+    return;
+  }

148-150: Complex ternary for title attribute.

The logic is functionally correct but hard to parse. Consider simplifying.

       title={
-        !children ? disabledMessage : (isDisabled ? disabledMessage : null)
+        (!children || isDisabled) ? disabledMessage : null
       }

This shows the intent more clearly: show the message when there are no children OR when disabled.


156-162: Nested interactive elements cause accessibility issues.

Same pattern as other action components — a <button> inside a <div role="button"> is problematic. Replace with a non-interactive element.

       {children ?? (
         <StyledWrapper className="flex items-center">
-          <button className="p-1">
+          <span className="p-1">
             <IconBookmark size={16} strokeWidth={2} />
-          </button>
+          </span>
         </StyledWrapper>
       )}
packages/bruno-app/src/components/ResponsePane/index.js (1)

206-212: Arrow functions recreated on every render.

The inline arrow functions for onFormatChange and onPreviewTabSelect create new function references each render. For a component this size, it's unlikely to cause performance issues, but memoization would be cleaner.

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

91-104: Unnecessary ResizeObserver recreation on item change.

The item in the dependency array causes the ResizeObserver to disconnect and reconnect whenever the item prop changes. Since checkSpace only reads DOM dimensions and doesn't depend on item data, this is wasteful.

 useEffect(() => {
   checkSpace();

   const rightSideContainer = actionsRef.current?.closest('.right-side-container');
   if (!rightSideContainer) return;

   const resizeObserver = new ResizeObserver(debouncedCheckSpace);
   resizeObserver.observe(rightSideContainer);

   return () => {
     resizeObserver.disconnect();
     debouncedCheckSpace.cancel();
   };
-}, [item, debouncedCheckSpace]);
+}, [debouncedCheckSpace]);
packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js (1)

75-79: Consider preventing default on Space key.

The keyboard handler follows the pattern from ResponseCopy and ResponseClear. However, Space key may cause page scroll. Adding e.preventDefault() would ensure consistent behavior.

   const handleKeyDown = (e) => {
     if (e.key === 'Enter' || e.key === ' ') {
+      e.preventDefault();
       toggleOrientation();
     }
   };
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (2)

46-68: Duplicate Buffer conversion logic across three locations.

The same Buffer.from(dataBuffer, 'base64') with try-catch appears in useInitialResponseFormat, useResponsePreviewFormatOptions, and the QueryResult component body. Extract this to a shared utility or custom hook.

// Consider a helper function in utils or a local helper
const safeBufferFromBase64 = (dataBuffer) => {
  if (!dataBuffer) return null;
  try {
    return Buffer.from(dataBuffer, 'base64');
  } catch (error) {
    console.error('Error converting dataBuffer to Buffer:', error);
    return null;
  }
};

Also applies to: 70-105, 118-125


156-158: Unused responseEncoding in dependency array.

responseEncoding is listed as a dependency but isn't used in the formattedData memo body.

-    [data, dataBuffer, responseEncoding, selectedFormat, filter, isLargeResponse, showLargeResponse]
+    [data, dataBuffer, selectedFormat, filter, isLargeResponse, showLargeResponse]
📜 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 88ce6eb and b434b82.

📒 Files selected for processing (38)
  • packages/bruno-app/src/components/ButtonDropdown/index.jsx (1 hunks)
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/StyledWrapper.js (2 hunks)
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js (0 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (4 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js (4 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseSave/StyledWrapper.js (0 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js (0 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/StatusCode/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/StatusCode/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/index.js (5 hunks)
  • packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js (2 hunks)
💤 Files with no reviewable changes (3)
  • packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseSave/StyledWrapper.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/bruno-app/src/components/ButtonDropdown/index.jsx
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx
🧰 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() not func ()
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/ResponsePane/StatusCode/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/StyledWrapper.js
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/index.js
  • packages/bruno-app/src/components/ResponsePane/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js
  • packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/StatusCode/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/StyledWrapper.js
  • packages/bruno-app/src/components/RunnerResults/ResponsePane/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 Component CSS may change layout, but Tailwind classes should not define colors

Applied to files:

  • packages/bruno-app/src/components/ResponsePane/StatusCode/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/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 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/ResponsePane/StatusCode/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/StyledWrapper.js
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/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} : In styled components and React components using styled components, use the theme prop to manage CSS colors instead of CSS variables

Applied to files:

  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/StyledWrapper.js
📚 Learning: 2025-12-02T09:45:31.698Z
Learnt from: sid-bruno
Repo: usebruno/bruno PR: 6266
File: packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js:38-38
Timestamp: 2025-12-02T09:45:31.698Z
Learning: In the ResponseCopy component (packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js), the copy button is intentionally disabled using `!response.data` to prevent copying stream resets which result in empty strings.

Applied to files:

  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js
  • packages/bruno-app/src/components/ResponsePane/index.js
🧬 Code graph analysis (18)
packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js (1)
packages/bruno-app/src/components/ResponsePane/ClearTimeline/StyledWrapper.js (1)
  • StyledWrapper (3-6)
packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js (3)
packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js (1)
  • StyledWrapper (3-13)
packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js (1)
  • StyledWrapper (3-12)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js (1)
  • StyledWrapper (3-12)
packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js (3)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (5)
  • useInitialResponseFormat (47-68)
  • useInitialResponseFormat (47-68)
  • useResponsePreviewFormatOptions (71-105)
  • useResponsePreviewFormatOptions (71-105)
  • QueryResult (107-245)
packages/bruno-app/src/components/ResponsePane/QueryResponse/StyledWrapper.js (1)
  • StyledWrapper (3-14)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js (1)
  • StyledWrapper (3-11)
packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/StyledWrapper.js (6)
packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/StyledWrapper.js (1)
  • StyledWrapper (3-330)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js (1)
  • StyledWrapper (3-11)
packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js (1)
  • StyledWrapper (3-58)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js (1)
  • StyledWrapper (3-12)
packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js (1)
  • StyledWrapper (3-12)
packages/bruno-app/src/components/ResponsePane/StyledWrapper.js (1)
  • StyledWrapper (3-42)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js (1)
packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js (1)
  • StyledWrapper (3-58)
packages/bruno-app/src/components/ResponsePane/ResponseTime/index.js (1)
packages/bruno-app/src/components/ResponsePane/StyledWrapper.js (1)
  • StyledWrapper (3-42)
packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js (1)
packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js (1)
  • ResponseDownload (8-61)
packages/bruno-app/src/components/ResponsePane/QueryResponse/StyledWrapper.js (3)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js (1)
  • StyledWrapper (3-11)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js (1)
  • StyledWrapper (3-12)
packages/bruno-app/src/components/ResponsePane/StyledWrapper.js (1)
  • StyledWrapper (3-42)
packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/index.js (1)
packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js (1)
  • QueryResponse (8-58)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (3)
packages/bruno-app/src/utils/common/index.js (2)
  • isValidHtml (487-490)
  • isValidHtml (487-490)
packages/bruno-app/src/utils/response/index.js (2)
  • isValidHtmlSnippet (97-169)
  • isValidHtmlSnippet (97-169)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (1)
  • baseUrl (84-84)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js (2)
packages/bruno-app/src/components/ResponsePane/index.js (1)
  • response (67-67)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js (1)
  • StyledWrapper (3-12)
packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js (2)
packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js (9)
  • useResponseLayoutToggle (52-70)
  • useResponseLayoutToggle (52-70)
  • useResponseLayoutToggle (73-73)
  • ResponseLayoutToggle (72-104)
  • orientation (55-55)
  • IconDockToBottom (6-26)
  • IconDockToBottom (6-26)
  • IconDockToRight (28-49)
  • IconDockToRight (28-49)
packages/bruno-app/src/components/Dropdown/index.js (1)
  • Dropdown (5-25)
packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js (1)
packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js (1)
  • StyledWrapper (3-13)
packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js (3)
packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js (1)
  • useResponseLayoutToggle (44-44)
packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js (1)
  • handleKeyDown (27-31)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js (1)
  • handleKeyDown (40-44)
packages/bruno-app/src/components/ResponsePane/index.js (2)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (4)
  • useInitialResponseFormat (47-68)
  • useInitialResponseFormat (47-68)
  • useResponsePreviewFormatOptions (71-105)
  • useResponsePreviewFormatOptions (71-105)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx (1)
  • QueryResultTypeSelector (7-40)
packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js (1)
packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js (1)
  • QueryResponse (8-58)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (4)
packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js (2)
  • useInitialResponseFormat (17-17)
  • selectedTab (20-20)
packages/bruno-app/src/utils/common/index.js (7)
  • dataBuffer (280-280)
  • dataBuffer (384-384)
  • contentType (93-93)
  • getContentType (83-119)
  • getContentType (83-119)
  • formatResponse (273-429)
  • formatResponse (273-429)
packages/bruno-app/src/utils/response/index.js (4)
  • detectContentTypeFromBuffer (176-258)
  • detectContentTypeFromBuffer (176-258)
  • getDefaultResponseFormat (8-59)
  • getDefaultResponseFormat (8-59)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (1)
  • QueryResultPreview (20-133)
packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js (1)
packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js (1)
  • QueryResponse (8-58)
⏰ 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: CLI Tests
  • GitHub Check: Unit Tests
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: SSL Tests - macOS
🔇 Additional comments (31)
packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js (1)

18-22: Styling change to StyledWrapper is safe and consistent

Removing the left margin and using className="flex items-center" keeps layout simple and doesn’t affect behavior; change looks good.

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

37-41: Separator styling looks good and follows theming guidelines

The .separator block is concise, uses theme-based color via props.theme.preferences.sidebar.border, and keeps spacing tight, which fits the existing styled-components pattern here. No issues from a layout/theming standpoint.

Based on learnings, this correctly uses the theme prop for colors in styled components.

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

5-5: LGTM! Consistent emphasis adjustment.

The font-weight increase to 600 aligns with similar status text emphasis in ResponseTime and StatusCode wrappers.

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

24-24: LGTM! Spacing adjustment aligns with layout updates.

The reduced margin (ml-4 → ml-2) is consistent with spacing standardization across ResponsePane components.

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

5-5: LGTM! Consistent emphasis adjustment.

Font-weight increase to 600 maintains visual consistency with other response status wrappers.

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

6-12: LGTM! Interactive styling aligns with action button pattern.

The hover states and theming match the pattern established in ResponseClear, ResponseCopy, and ResponseDownload wrappers.

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

4-5: Layout system change from grid to flex.

The shift from grid to flexbox better supports the new modular preview components. Verify that CodeMirror editor sizing (absolute positioning hack at lines 8-18) and PDF viewer still render correctly across different response types.

Manually test the following scenarios:

  • JSON response rendering in CodeMirror editor
  • PDF response rendering with react-pdf
  • HTML/XML preview rendering
  • Switching between different response format types
packages/bruno-app/src/components/ResponsePane/QueryResponse/StyledWrapper.js (1)

1-16: LGTM! Clean wrapper with consistent theming.

The flex-column layout and border theming align with the broader ResponsePane refactor. The nested .query-response-content border provides clear visual separation for response content.

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

6-6: Component replacement requires verification against the full codebase.

The transition from ResponseSave to ResponseDownload at lines 6 and 29 appears correct structurally, but verification that all remaining ResponseSave references have been removed cannot be completed due to repository access limitations. Run the provided verification script locally or ensure the following checks pass:

  • No ResponseSave imports remain in the codebase
  • No <ResponseSave JSX usages exist
  • No orphaned ResponseSave files or exports remain
  • All call sites use the new ResponseDownload component with the expected props (item, asDropdownItem, onClose)
packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/StyledWrapper.js (1)

258-306: Response body tab strip styling looks consistent and theme‑driven

The .response-body-container and nested div[role="tablist"] > div styles cleanly align the response preview tabs with console theming, without introducing layout risks. Looks good to me.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (1)

9-32: Drag‑guard via MutationObserver is reasonable

Watching a common ancestor for .dragging and flipping pointerEvents/userSelect on the container and <webview> is a pragmatic way to avoid interaction glitches while resizing/dragging. The effect is well‑scoped and cleaned up via disconnect().

Also applies to: 39-52

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

21-25: Spacing tweak on size label is fine

Reducing the left margin to ml-2 keeps the existing size formatting logic intact while aligning spacing with the rest of the status bar.

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

3-11: Clear‑button styling aligns with shared theme patterns

Using theme.codemirror.variable.info.iconColor plus the shared hover background/text tokens and a 4px radius keeps the Clear action visually consistent with the other response toolbar controls. Looks good.

Based on learnings, this correctly relies on theme colors rather than raw CSS variables.

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

20-21: Margin adjustment on response time is safe

Switching the wrapper to ml-2 only affects spacing; the duration formatting and isNumber guard remain intact.

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

4-18: Layout toggle button theming is consistent with other controls

Deriving the icon color from theme.codemirror.variable.info.iconColor and adding the shared hover background/text styling keeps the layout toggle visually in line with the rest of the response toolbar.

Based on learnings, this is the expected pattern of using styled‑components with theme tokens for colors.

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

4-5: Runner results now correctly route through QueryResponse

Swapping to <QueryResponse> in the “response” tab keeps the existing props wiring (item/collection/data/dataBuffer/headers/error/disableRunEventListener) while aligning this pane with the new response‑format/preview flow. The change is self‑contained and looks correct.

Also applies to: 41-52

packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/index.js (1)

10-10: LGTM: Component swap aligned with refactor.

The QueryResult → QueryResponse migration is consistent with the broader refactor pattern across the codebase. Props remain unchanged, maintaining the same API surface.

Also applies to: 119-127

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

5-6: LGTM: Visual refinement for status display.

The font-weight increase and nowrap constraint improve status code readability without affecting functionality.

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

5-11: LGTM: Theming aligned with CodeMirror conventions.

The updated color tokens and hover state provide visual consistency across ResponsePane components. Theme usage follows coding guidelines.

packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js (1)

1-1: LGTM: Component migration consistent with refactor.

The QueryResult → QueryResponse swap maintains the same prop interface and aligns with the broader refactor pattern.

Also applies to: 17-25

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

3-12: LGTM: Consistent styling pattern.

The wrapper follows established theming conventions and matches the styling pattern used across other ResponsePane action components (ResponseCopy, ResponseClear, ResponseBookmark).

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

7-35: LGTM: Clean hook extraction.

Extracting the copy logic into useResponseCopy improves reusability and testability. The hook properly manages state and cleanup.


37-67: LGTM: Children prop enhances flexibility.

Adding support for custom rendering via the children prop improves component reusability while maintaining backward compatibility with the default button rendering.

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

41-46: Format state may become stale when switching between requests.

When switching to a different request item, selectedFormat and selectedTab only update if initialFormat/initialTab are non-null. If the new item has no response yet, the previous item's format settings persist, which may confuse users.

Consider resetting to defaults when the item changes:

 useEffect(() => {
   if (initialFormat !== null && initialTab !== null) {
     setSelectedFormat(initialFormat);
     setSelectedTab(initialTab);
+  } else {
+    setSelectedFormat('raw');
+    setSelectedTab('editor');
   }
 }, [initialFormat, initialTab]);

Alternatively, add item.uid as a dependency to force reset on item change.

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

126-133: Action may not execute when dropdown closes.

closeDropdown is called synchronously on click, which hides the Tippy dropdown. Depending on Tippy's implementation, this might unmount children before ResponseCopy's click handler completes. Consider deferring the close or ensuring action completion first.

Verify dropdown items work correctly in the UI. If issues occur, defer close:

 <ResponseCopy item={item}>
-  <div className="dropdown-item" onClick={closeDropdown}>
+  <div className="dropdown-item" onClick={() => setTimeout(closeDropdown, 0)}>

31-41: LGTM on MenuIcon implementation.

Clean use of forwardRef with proper displayName for DevTools debugging.


120-184: Solid responsive action bar implementation.

The space detection logic with ResizeObserver and the fallback to a dropdown menu is well thought out for handling varying viewport widths. Good separation between collapsed and expanded states.

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

6-26: LGTM on icon exports.

Exporting IconDockToBottom and IconDockToRight enables reuse across the codebase. The SVG structure is consistent.

Also applies to: 28-49


51-70: LGTM on useResponseLayoutToggle hook.

Clean extraction of orientation state and toggle logic. This aligns with usage in ResponsePaneActions and promotes reusability.

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

13-31: LGTM on format options constant.

The PREVIEW_FORMAT_OPTIONS structure is well-organized with clear groupings and CodeMirror mode mappings.


222-236: LGTM on QueryResultPreview integration.

The component properly passes all required props including the new selectedTab, previewMode, and codeMirrorMode.

Comment on lines +84 to +102
<div
role="button"
tabIndex={0}
onClick={toggleOrientation}
title={title}
onKeyDown={handleKeyDown}
>
{children ? children : (
<StyledWrapper className="flex items-center w-full">
<button className="p-1">
{orientation === 'horizontal' ? (
<IconDockToBottom />
) : (
<IconDockToRight />
)}
</button>
</StyledWrapper>
)}
</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 | 🟡 Minor

Nested interactive elements create accessibility issues.

The outer <div> with role="button" wraps a <button> (line 93). This creates nested interactive elements which is problematic for screen readers and keyboard navigation. When children is not provided, clicking the inner button also triggers the outer div's click handler.

Consider removing the inner <button> when rendering the default UI:

       {children ? children : (
         <StyledWrapper className="flex items-center w-full">
-          <button className="p-1">
+          <span className="p-1">
             {orientation === 'horizontal' ? (
               <IconDockToBottom />
             ) : (
               <IconDockToRight />
             )}
-          </button>
+          </span>
         </StyledWrapper>
       )}
📝 Committable suggestion

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

Suggested change
<div
role="button"
tabIndex={0}
onClick={toggleOrientation}
title={title}
onKeyDown={handleKeyDown}
>
{children ? children : (
<StyledWrapper className="flex items-center w-full">
<button className="p-1">
{orientation === 'horizontal' ? (
<IconDockToBottom />
) : (
<IconDockToRight />
)}
</button>
</StyledWrapper>
)}
</div>
<div
role="button"
tabIndex={0}
onClick={toggleOrientation}
title={title}
onKeyDown={handleKeyDown}
>
{children ? children : (
<StyledWrapper className="flex items-center w-full">
<span className="p-1">
{orientation === 'horizontal' ? (
<IconDockToBottom />
) : (
<IconDockToRight />
)}
</span>
</StyledWrapper>
)}
</div>
🤖 Prompt for AI Agents
packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js
lines 84-102: the outer div with role="button" currently contains a real
<button>, creating nested interactive elements; remove the inner <button> when
rendering the default UI and render the icon directly inside the StyledWrapper
so there is only one interactive control. Keep the outer element as the single
interactive control (or convert it to a native <button>), retain title, onClick,
tabIndex and onKeyDown handlers, and add appropriate ARIA (e.g., aria-pressed or
aria-label) so keyboard (Enter/Space) and screen readers behave correctly.

@abhishek-bruno abhishek-bruno force-pushed the feat/response-data-type-selector branch from b434b82 to dac8f7d Compare December 8, 2025 06:25
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: 8

Caution

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

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

8-15: Duplicate keys in classnames object break status coloring logic

The object passed to classnames contains duplicate keys for both 'text-ok' and 'text-error'. In JavaScript object literals, later property definitions overwrite earlier ones, so the effective behavior is:

  • 'text-ok' only for 200 <= status < 300
  • 'text-error' only for 500 <= status < 600

Responses with status codes 100–199, 300–399, and 400–499 receive no status color class, which defeats the purpose of the color indicator.

Fix by collapsing the ranges to eliminate duplicate keys:

  const getTabClassname = (status) => {
    return classnames({
-     'text-ok': status >= 100 && status < 200,
-     'text-ok': status >= 200 && status < 300,
-     'text-error': status >= 300 && status < 400,
-     'text-error': status >= 400 && status < 500,
-     'text-error': status >= 500 && status < 600
+     'text-ok': status >= 100 && status < 300,
+     'text-error': status >= 300 && status < 600
    });
  };
♻️ Duplicate comments (7)
packages/bruno-app/src/components/ErrorAlert/StyledWrapper.js (1)

8-8: Prefer theme radius token instead of hard-coded border-radius

border-radius: 0.375rem; should ideally use the shared radius token from the theme (e.g. whatever the base/small radius is) for consistency with the rest of the design system.

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

542-566: switchToEditorTab still clicks the preview tab

switchToEditorTab is currently identical to switchToPreviewTab and still clicks preview-response-tab, so it doesn’t actually ensure the editor view is active and makes tests misleading.

Update switchToEditorTab to target the editor control (whatever test id represents it) or otherwise distinguish its behavior from the preview helper.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (1)

87-88: Add alt attribute to image for accessibility.

Missing alt attribute on the image element.

-      return <img src={`data:${contentType.replace(/\;(.*)/, '')};base64,${dataBuffer}`} />;
+      return <img src={`data:${contentType.replace(/\;(.*)/, '')};base64,${dataBuffer}`} alt="Response preview" />;
packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js (1)

91-100: Nested interactive elements create accessibility issues.

The outer <div> with role="button" wraps a <button> (line 93). This creates nested interactive elements which is problematic for screen readers and keyboard navigation.

       {children ? children : (
         <StyledWrapper className="flex items-center w-full">
-          <button className="p-1">
+          <span className="p-1">
             {orientation === 'horizontal' ? (
               <IconDockToBottom />
             ) : (
               <IconDockToRight />
             )}
-          </button>
+          </span>
         </StyledWrapper>
       )}
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (3)

186-190: Fallback CodeMirror mode is non-standard.

'application/text' is not a valid MIME type. Use 'text/plain' instead.

-      .find((option) => option.value === selectedFormat)?.codeMirrorMode || 'application/text';
+      .find((option) => option.value === selectedFormat)?.codeMirrorMode || 'text/plain';

86-88: isByteFormatType will throw if contentType is null.

If getContentTypeToCheck() returns null, calling .includes() on it will throw a TypeError.

     const isByteFormatType = (contentType) => {
+      if (!contentType) return false;
       return byteFormatTypes.some((type) => contentType.includes(type));
     };

165-184: Missing fallback return and incorrect dependency.

The useMemo can return undefined implicitly. Also, contentType is in the dependency array but detectedContentType (which is actually used) is not.

       // for all other content types, return preview-text
       return 'preview-text';
     }
+    return 'preview-text'; // fallback for unhandled formats
-  }, [selectedFormat, contentType, dataBuffer]);
+  }, [selectedFormat, detectedContentType]);
🧹 Nitpick comments (17)
packages/bruno-app/src/components/ErrorAlert/index.js (1)

5-22: Solid ErrorAlert implementation; consider minor refinements for message handling and accessibility

Overall component behavior looks good and fits the existing styling approach.

Two small, optional polish points:

  • Line 6: if (!message) return null will also hide intentionally falsy-but-valid values (e.g. 0, false). If you ever expect those, you might want a stricter check like message == null instead.
  • Lines 13 & 15–18: For non‑string messages, JSON.stringify works for plain objects but will produce {} for Error instances and can throw on circular structures. Also, the close affordance is a div with onClick; switching to a <button type="button"> with an aria-label would improve accessibility and keyboard operability.

These are non-blocking; fine to defer if they don’t match current usage.

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

13-13: Consider moving background colors to theme tokens instead of hard-coded RGBA

The conditional background:

background-color: ${(props) => (props.theme.bg === '#1e1e1e' ? 'rgba(40, 40, 40, 0.5)' : 'rgba(250, 250, 250, 0.9)')};

relies on a specific hex and literal RGBA values. Wiring these through semantic theme tokens (e.g. dedicated surface/alert background colors) would make it easier to adjust themes and avoid coupling to a single bg hex value.

Based on learnings, using theme-driven colors in styled components keeps them consistent across themes.

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

472-479: Avoid keeping commented-out legacy openRequest

If the old, request-only openRequest is no longer needed, you can drop this commented block and rely on git history instead of keeping dead code in the helper file.


480-494: Restore a post-condition assertion and consider centralizing locators

The new openRequest(page, collectionName, requestName) correctly scopes the request to its collection, but you dropped the previous assertion that the active request tab matches requestName. Re‑adding that check would make tests more robust and keep failures localized.

You might also consider factoring this DOM traversal into buildCommonLocators (e.g., a sidebar.collectionRequest(collectionName, requestName) helper) so selectors live in one place.


510-527: Merge default timeout with overrides or delegate to sendRequest

sendRequestAndWaitForResponse is handy, but callers that pass { ignoreCase: true } lose the default timeout: 15000. Either:

  • Merge options with a default inside the function, e.g. const finalOptions = { timeout: 15000, ...options }, or
  • Delegate to sendRequest to keep a single place that clicks “send” and waits for the status element.

This avoids subtle differences in behavior and duplicated logic.


529-540: Optional: tighten typing for format

Implementation looks fine. If you already have a central enum/union for response formats, consider typing format as that union instead of string to catch typos at compile time.

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

9-11: Consider hardening against missing ipcRenderer / item in non‑Electron contexts

Destructuring const { ipcRenderer } = window and assuming item is always defined will throw in tests or any non‑Electron environment that mounts this component. If this component can be rendered in such contexts, consider guarding:

  • Use const ipcRenderer = window?.ipcRenderer and early‑return (or no‑op + warning) when it’s absent
  • Optionally use const response = item?.response || {} and item?.pathname for a bit more safety

This stays cheap at runtime while making the component more robust to accidental reuse.


13-26: Avoid wrapping ipcRenderer.invoke in a manual Promise and improve error extraction

ipcRenderer.invoke already returns a Promise, so the extra new Promise wrapper is unnecessary. You can simplify and keep the same behavior:

-  const saveResponseToFile = () => {
-    if (isDisabled) {
-      return;
-    }
-    return new Promise((resolve, reject) => {
-      ipcRenderer
-        .invoke('renderer:save-response-to-file', response, item?.requestSent?.url, item.pathname)
-        .then(resolve)
-        .catch((err) => {
-          toast.error(get(err, 'error.message') || 'Something went wrong!');
-          reject(err);
-        });
-    });
-  };
+  const saveResponseToFile = () => {
+    if (isDisabled) {
+      return;
+    }
+
+    return ipcRenderer
+      .invoke('renderer:save-response-to-file', response, item?.requestSent?.url, item.pathname)
+      .catch((err) => {
+        const message =
+          get(err, 'message') ||
+          get(err, 'error.message') ||
+          'Something went wrong!';
+        toast.error(message);
+        throw err;
+      });
+  };

This removes a Promise anti‑pattern and also makes the error message more resilient if the rejection is a plain Error.


28-38: Clean up button semantics to avoid nested interactive controls

Right now the outer element is a div with role="button" / tabIndex and keyboard handling, but the default UI also renders a nested <button>. This leads to:

  • Two focusable/interactive elements (outer div and inner button) for a single action
  • Confusing semantics for assistive tech and an extra tab stop

Since the outer div is already the interactive surface, you can drop the inner <button> and keep the click/keyboard logic on the wrapper:

-      className={classnames({
-        'opacity-50 cursor-not-allowed': isDisabled
-      })}
+      className={classnames({
+        'opacity-50 cursor-not-allowed': isDisabled
+      })}
     >
-      {children ? children : (
-        <StyledWrapper className="flex items-center">
-          <button className="p-1">
-            <IconDownload size={16} strokeWidth={2} />
-          </button>
-        </StyledWrapper>
-      )}
+      {children ? children : (
+        <StyledWrapper className="flex items-center p-1">
+          <IconDownload size={16} strokeWidth={2} />
+        </StyledWrapper>
+      )}

This keeps the existing visual structure and keyboard behavior while avoiding nested interactive controls.

Also applies to: 40-59

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

43-185: Responsive actions bar logic looks solid

The measurement logic (siblings + padding) with a debounced ResizeObserver and the showMenuRef/individualButtonsWidthRef pattern avoids stale state and flicker when switching between inline buttons and the overflow menu. The early return for non‑http-request items also keeps this scoped to HTTP responses.

If you ever need this to run in environments without ResizeObserver (e.g., some tests), a small feature check around its usage would make it even more robust, but it isn’t a blocker for an Electron app.

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

7-9: New formatting pipeline (HTML/JS/hex/base64/text) looks coherent, with minor perf nits

Overall this extension of formatResponse and the new helpers hangs together well:

  • getContentType’s JAVASCRIPT_PATTERN and mapping to 'application/javascript' aligns with the new JavaScript mode handling elsewhere.
  • The html and javascript branches both honor isVeryLargeResponse before attempting pretty‑printing, and fall back cleanly to the original string if formatting fails.
  • Hex handling (mode.includes('hex')) correctly avoids re-encoding strings already detected as hex via isHexFormat, and otherwise generates a standard hex+ASCII dump using formatHexView.
  • The text/raw branch reuses rawData for small responses and falls back to type-aware stringification for large payloads, matching the intent to avoid decoding very large buffers.

Two small, non-blocking nits you might consider (now or later):

  • For very large but still <50MB payloads, pretty‑printing JS/HTML or generating a full hex dump can still be expensive; if you see UI stalls in practice, it may be worth introducing a lower, format-specific threshold (especially for the hex view).
  • In the hex branch, you could potentially reuse the already-decoded buffer from the top of formatResponse instead of re‑decoding dataBufferString, but that’s a micro-optimization and not required for correctness.

Nothing here looks functionally off; the behavior matches the new preview modes.

Also applies to: 105-116, 273-422, 466-557

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

19-22: Response format/tab wiring and header layout look consistent

The new state + hooks flow is clean:

  • useInitialResponseFormat / useResponsePreviewFormatOptions are wired with item.response?.dataBuffer and headers, and the effect only initializes selectedFormat / selectedTab once valid initial values are available—so user changes aren’t overwritten on re-render.
  • Passing selectedFormat and selectedTab into QueryResult gives the preview pipeline the context it needs without bloating props elsewhere.
  • The header’s right-side container nicely groups the preview selector, status (code/time/size), and actions, and only shows the selector when the Response tab is focused and the pane isn’t loading.

One minor UI edge case to be aware of: for non‑http-request items with a successful response, the parent still renders the actions container, but ResponsePaneActions will early-return null, leaving an empty actions area between separators. If that ever becomes visible for WebSocket or other request types, a parent-level type check before rendering the actions block would tidy it up.

Also applies to: 34-47, 85-103, 173-238

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

53-72: Redundant and inefficient guard checks.

The isResponseTooLarge check (lines 61-66) is redundant since isDisabled already encompasses it. Additionally, the isDisabled check (lines 68-72) should be at the top to short-circuit before showing individual error toasts.

   const handleSaveClick = (e) => {
+    if (isDisabled) {
+      e.preventDefault();
+      e.stopPropagation();
+      return;
+    }
+
     if (!response || response.error) {
       toast.error('No valid response to save as example');
       e.preventDefault();
       e.stopPropagation();
       return;
     }
 
-    if (isResponseTooLarge) {
-      toast.error('Response size exceeds 5MB limit. Cannot save as example.');
-      e.preventDefault();
-      e.stopPropagation();
-      return;
-    }
-
-    if (isDisabled) {
-      e.preventDefault();
-      e.stopPropagation();
-      return;
-    }
-
     setShowSaveResponseExampleModal(true);
   };
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (1)

1-1: Remove unused useMemo import.

useMemo is imported but never used in this file.

-import React, { useState, useMemo } from 'react';
+import React, { useState } from 'react';
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview.js (2)

182-188: Consider keyboard accessibility for expand/collapse buttons.

Using tabIndex={-1} removes these buttons from keyboard navigation. While focus is managed through the parent, users relying solely on keyboard may not be able to expand/collapse individual nodes.

Consider using tabIndex={0} and adding keyboard event handling for Enter/Space to toggle expansion.


27-32: Move isValidTreeData outside component.

This function is recreated on every render. Since it has no dependencies on component state or props, move it outside the component.

+const isValidTreeData = (data) => {
+  if (data === null || data === undefined) return false;
+  if (typeof data === 'object' && !Array.isArray(data)) return true;
+  if (Array.isArray(data)) return true;
+  return false;
+};
+
 export default function XmlPreview({ data, defaultExpanded = true }) {
   // ...
-  const isValidTreeData = (data) => {
-    if (data === null || data === undefined) return false;
-    if (typeof data === 'object' && !Array.isArray(data)) return true;
-    if (Array.isArray(data)) return true;
-    return false;
-  };
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (1)

46-68: Consider extracting duplicate buffer conversion logic.

The Buffer.from(dataBuffer, 'base64') conversion with try-catch is repeated in useInitialResponseFormat, useResponsePreviewFormatOptions, and in the component itself. Extract to a shared utility.

// utils/buffer.js or inline helper
const safeBase64ToBuffer = (dataBuffer) => {
  if (!dataBuffer) return null;
  try {
    return Buffer.from(dataBuffer, 'base64');
  } catch (error) {
    console.error('Error converting dataBuffer to Buffer:', error);
    return null;
  }
};

Also applies to: 71-105, 118-124

📜 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 b434b82 and dac8f7d.

📒 Files selected for processing (58)
  • packages/bruno-app/src/components/ButtonDropdown/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ButtonDropdown/index.jsx (1 hunks)
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/StyledWrapper.js (2 hunks)
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/index.js (2 hunks)
  • packages/bruno-app/src/components/ErrorAlert/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ErrorAlert/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js (0 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/JsonPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/TextPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/VideoPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (4 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (4 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js (4 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseSave/StyledWrapper.js (0 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js (0 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/StatusCode/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/StatusCode/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/index.js (5 hunks)
  • packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js (2 hunks)
  • packages/bruno-app/src/utils/common/codemirror.js (1 hunks)
  • packages/bruno-app/src/utils/common/index.js (4 hunks)
  • packages/bruno-app/src/utils/response/index.js (1 hunks)
  • tests/response/large-response-crash-prevention.spec.ts (1 hunks)
  • tests/response/response-format-select-and-preview/fixtures/collection/bruno.json (1 hunks)
  • tests/response/response-format-select-and-preview/fixtures/collection/request-html.bru (1 hunks)
  • tests/response/response-format-select-and-preview/fixtures/collection/request-json.bru (1 hunks)
  • tests/response/response-format-select-and-preview/init-user-data/collection-security.json (1 hunks)
  • tests/response/response-format-select-and-preview/init-user-data/preferences.json (1 hunks)
  • tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts (1 hunks)
  • tests/utils/page/actions.ts (3 hunks)
  • tests/utils/page/locators.ts (2 hunks)
💤 Files with no reviewable changes (3)
  • packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js
  • packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseSave/StyledWrapper.js
✅ Files skipped from review due to trivial changes (2)
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/StyledWrapper.js
🚧 Files skipped from review as they are similar to previous changes (29)
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/index.js
  • tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js
  • packages/bruno-app/src/components/ButtonDropdown/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/JsonPreview.js
  • packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js
  • tests/response/response-format-select-and-preview/fixtures/collection/request-html.bru
  • tests/response/large-response-crash-prevention.spec.ts
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js
  • packages/bruno-app/src/components/ButtonDropdown/index.jsx
  • packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js
  • tests/response/response-format-select-and-preview/init-user-data/preferences.json
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/StyledWrapper.js
  • tests/utils/page/locators.ts
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js
  • packages/bruno-app/src/components/ResponsePane/StyledWrapper.js
  • packages/bruno-app/src/utils/response/index.js
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/index.js
  • tests/response/response-format-select-and-preview/fixtures/collection/bruno.json
🧰 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/ResponsePane/ClearTimeline/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js
  • packages/bruno-app/src/components/ErrorAlert/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview.js
  • packages/bruno-app/src/components/ErrorAlert/StyledWrapper.js
  • packages/bruno-app/src/utils/common/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js
  • tests/utils/page/actions.ts
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/VideoPreview.js
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/TextPreview.js
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js
  • packages/bruno-app/src/utils/common/codemirror.js
  • packages/bruno-app/src/components/ResponsePane/StatusCode/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js
  • packages/bruno-app/src/components/ResponsePane/StatusCode/index.js
  • packages/bruno-app/src/components/ResponsePane/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseSize/index.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/utils/page/actions.ts
  • tests/response/response-format-select-and-preview/init-user-data/collection-security.json
  • tests/response/response-format-select-and-preview/fixtures/collection/request-json.bru
🧠 Learnings (6)
📚 Learning: 2025-12-05T20:31:32.975Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:32.975Z
Learning: Applies to **/*.{jsx,tsx} : Styled Component CSS might also change layout but Tailwind classes shouldn't define colors

Applied to files:

  • packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js
  • packages/bruno-app/src/components/ErrorAlert/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/StatusCode/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js
📚 Learning: 2025-12-05T20:31:32.975Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:32.975Z
Learning: Applies to **/*.{jsx,tsx} : Styled 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/ErrorAlert/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/StatusCode/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js
📚 Learning: 2025-12-05T20:31:32.975Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:32.975Z
Learning: Applies to **/*.{jsx,tsx} : Use styled component's theme prop to manage CSS colors and not CSS variables when in the context of a styled component or any React component using the styled component

Applied to files:

  • packages/bruno-app/src/components/ErrorAlert/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/StatusCode/StyledWrapper.js
📚 Learning: 2025-12-05T20:31:32.975Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:32.975Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Applied to files:

  • packages/bruno-app/src/utils/common/index.js
📚 Learning: 2025-12-05T20:31:32.975Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:32.975Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Add in JSDoc comments to add more details to the abstractions if needed

Applied to files:

  • packages/bruno-app/src/utils/common/index.js
📚 Learning: 2025-12-02T09:45:31.698Z
Learnt from: sid-bruno
Repo: usebruno/bruno PR: 6266
File: packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js:38-38
Timestamp: 2025-12-02T09:45:31.698Z
Learning: In the ResponseCopy component (packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js), the copy button is intentionally disabled using `!response.data` to prevent copying stream resets which result in empty strings.

Applied to files:

  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js
  • packages/bruno-app/src/components/ResponsePane/index.js
🧬 Code graph analysis (11)
packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js (2)
packages/bruno-app/src/components/ResponsePane/StyledWrapper.js (1)
  • StyledWrapper (3-42)
packages/bruno-app/src/components/ResponsePane/ClearTimeline/StyledWrapper.js (1)
  • StyledWrapper (3-6)
packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js (2)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js (1)
  • handleKeyDown (40-44)
packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js (1)
  • StyledWrapper (3-12)
packages/bruno-app/src/components/ErrorAlert/index.js (1)
packages/bruno-app/src/components/ErrorAlert/StyledWrapper.js (1)
  • StyledWrapper (3-42)
packages/bruno-app/src/utils/common/index.js (1)
packages/bruno-js/src/bru.js (1)
  • xmlFormat (2-2)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js (2)
packages/bruno-app/src/components/ResponsePane/index.js (1)
  • response (67-67)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js (1)
  • StyledWrapper (3-12)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/VideoPreview.js (2)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (1)
  • contentType (126-126)
packages/bruno-app/src/utils/common/index.js (3)
  • contentType (93-93)
  • dataBuffer (280-280)
  • dataBuffer (384-384)
packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js (3)
packages/bruno-app/src/components/ResponsePane/index.js (2)
  • response (67-67)
  • responseSize (69-83)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (1)
  • responseSize (132-147)
packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js (1)
  • StyledWrapper (3-13)
packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js (5)
packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js (1)
  • useResponseLayoutToggle (44-44)
packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js (1)
  • handleKeyDown (41-51)
packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js (1)
  • handleKeyDown (27-31)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js (1)
  • handleKeyDown (40-44)
packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js (1)
  • handleKeyDown (28-38)
packages/bruno-app/src/utils/common/codemirror.js (2)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (1)
  • contentType (126-126)
packages/bruno-app/src/utils/common/index.js (1)
  • contentType (93-93)
packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js (3)
packages/bruno-app/src/components/ResponsePane/index.js (1)
  • response (67-67)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js (1)
  • StyledWrapper (3-12)
packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js (1)
  • StyledWrapper (3-12)
packages/bruno-app/src/components/ResponsePane/index.js (3)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (5)
  • useInitialResponseFormat (47-68)
  • useInitialResponseFormat (47-68)
  • useResponsePreviewFormatOptions (71-105)
  • useResponsePreviewFormatOptions (71-105)
  • responseSize (132-147)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx (1)
  • QueryResultTypeSelector (7-40)
packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js (1)
  • ResponsePaneActions (43-186)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: Unit Tests
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: CLI Tests
🔇 Additional comments (21)
tests/response/response-format-select-and-preview/init-user-data/collection-security.json (1)

1-10: Well-structured fixture configuration with appropriate security defaults.

The JSON is valid, fixtures are properly nested per guidelines, and jsSandboxMode: "safe" is the right choice for testing HTML/JS preview rendering without risking malicious execution.

tests/response/response-format-select-and-preview/fixtures/collection/request-json.bru (1)

1-17: Fixture structure and placement look correct

The .bru fixture is well-formed (meta, post block, and body:json block) and the file path follows the expected tests/.../fixtures/collection layout for e2e collections. No changes needed here.

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

18-18: Verify visual spacing after removing left margin.

The removal of ml-2 reduces left margin spacing on the ClearTimeline button. Ensure this doesn't create visual crowding or misalignment with adjacent ResponsePane elements, particularly in the context of the broader layout adjustments mentioned in this PR.

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

7-22: LGTM! Clean hook extraction.

The useResponseClear hook encapsulates the dispatch logic nicely and provides a reusable abstraction.

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

6-35: LGTM! Clean hook extraction.

The hook encapsulates the copy logic well, properly manages state and side effects, and exposes a clear API. The hasData check aligns with the existing pattern of preventing copies when response data is unavailable.


37-38: LGTM! Component signature properly updated.

The addition of the children prop enables custom rendering while maintaining the default behavior. Hook usage is clean and idiomatic.


46-50: LGTM! Click handler is straightforward.

The guard ensures copy only happens when data is available.

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

612-616: Export additions look consistent

Exporting the new helpers (sendRequestAndWaitForResponse, switchResponseFormat, switchToPreviewTab, switchToEditorTab) from this barrel is consistent with the existing pattern and keeps test utilities discoverable.

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

3-14: StatusCode wrapper typography + nowrap look good

Using font-weight: 600 and white-space: nowrap here is reasonable for making the status more prominent and keeping it on a single line, and colors still come from the theme via .text-ok / .text-error, which aligns with our styled-components theming guidance. Based on learnings, this is consistent with how we want colors handled.

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

8-12: Confirm disabled condition vs other response actions

isDisabled is keyed off !response.dataBuffer, while ResponseCopy is intentionally disabled via !response.data to avoid copying stream resets that yield empty strings. Please double‑check that using dataBuffer here is intentional and matches how streamed responses are buffered and exposed for downloads, so we don’t end up enabling download when the underlying data is still incomplete or unavailable. Based on learnings, this difference is worth verifying.


40-51: Overall interaction wiring looks solid

The combination of role="button", tabIndex, aria-disabled, disabled styling, and the isDisabled guard in saveResponseToFile gives you a predictable UX and prevents accidental downloads when there’s no buffer. Aside from the minor refinements noted above, the interaction model is sound.

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

21-24: Spacing tweak is fine

Reducing the left margin on ResponseSize to ml-2 is a safe visual-only change; no functional impact on size computation or tooltip.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js (1)

1-13: Theme-based styling looks consistent

Using props.theme.colors.text.yellow for .active and text.muted for the label matches the styled-components + Tailwind split (colors in theme, layout elsewhere). No issues here.

packages/bruno-app/src/utils/common/codemirror.js (1)

104-108: JavaScript content-type mapping looks correct

Adding the javascript / ecmascript check and returning 'application/javascript' fits the existing pattern and should give JS responses an appropriate editor mode instead of plain text.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/TextPreview.js (1)

3-25: TextPreview’s normalization logic is sound

The memoized displayData correctly handles null/undefined, objects (with a safe JSON.stringify), and primitives via String(data), and the wrapper uses only layout/typography classes. This is a solid generic text preview.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/VideoPreview.js (1)

5-28: Harden the effect against invalid contentType / dataBuffer

The useEffect lacks guards for its inputs, so bad data (or missing contentType) could throw and break rendering. Add defensive checks:

-  useEffect(() => {
-    const videoType = contentType.split(';')[0];
-    const byteArray = Buffer.from(dataBuffer, 'base64');
-    const blob = new Blob([byteArray], { type: videoType });
-    const url = URL.createObjectURL(blob);
-    setVideoUrl(url);
-    return () => URL.revokeObjectURL(url);
-  }, [contentType, dataBuffer]);
+  useEffect(() => {
+    if (!contentType || !dataBuffer) {
+      setVideoUrl(null);
+      return;
+    }
+
+    let url;
+    try {
+      const videoType = contentType.split(';')[0];
+      const byteArray = Buffer.from(dataBuffer, 'base64');
+      const blob = new Blob([byteArray], { type: videoType });
+      url = URL.createObjectURL(blob);
+      setVideoUrl(url);
+    } catch (error) {
+      console.error('Error preparing video preview:', error);
+      setVideoUrl(null);
+    }
+
+    return () => {
+      if (url) {
+        URL.revokeObjectURL(url);
+      }
+    };
+  }, [contentType, dataBuffer]);
packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js (1)

41-51: LGTM on keyboard accessibility.

Good addition of keyboard event handling for Enter and Space keys, consistent with other ResponsePane components.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (1)

64-79: LGTM on editor tab rendering.

Clean conditional rendering for the editor path with appropriate props passed to CodeEditor.

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

51-70: LGTM on the new hook extraction.

Clean separation of concerns. The useResponseLayoutToggle hook nicely encapsulates the orientation state and toggle logic for reuse.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview.js (1)

5-68: LGTM on the overall XmlPreview structure.

Good use of useMemo for parsing, proper error handling with ErrorAlert, and clean data validation flow.

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

220-236: LGTM on QueryResultPreview integration.

Clean prop passing to the refactored preview component with the new format-driven architecture.

Comment on lines +231 to +240
<button
onClick={(e) => {
e.stopPropagation();
const target = e.currentTarget.parentElement.parentElement;
const content = target.querySelector('.array-content');
if (content) {
content.classList.toggle('hidden');
e.currentTarget.textContent = content.classList.contains('hidden') ? '▶' : '▼';
}
}}
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

DOM manipulation bypasses React reconciliation.

Direct DOM manipulation via parentElement, querySelector, and classList.toggle is an anti-pattern in React. This can cause state/UI inconsistencies. Use React state instead.

Extract array child rendering into a separate component with its own expansion state:

const ArrayChildNode = ({ nodeKey, value, depth }) => {
  const [expanded, setExpanded] = useState(true);
  
  return (
    <div style={{ paddingLeft: `${(depth + 1) * 20}px` }}>
      <div className="flex items-center mb-1">
        <button
          onClick={(e) => {
            e.stopPropagation();
            setExpanded((v) => !v);
          }}
          className="mr-2 cursor-pointer w-4 h-4 flex items-center justify-center text-[#666] dark:text-[#569cd6] flex-shrink-0 rounded transition-colors hover:bg-[#e0e0e0] dark:hover:bg-[#2d2e30]"
          tabIndex={-1}
        >
          {expanded ? '▼' : '▶'}
        </button>
        <span className="text-black dark:text-[#f9f8f5] font-medium">{nodeKey}</span>
        <span className="text-[#666] dark:text-[#808080] ml-2">[{value.length}]</span>
      </div>
      {expanded && value.map((item, itemIdx) => (
        <XmlNode
          key={itemIdx}
          node={item}
          nodeName={`${itemIdx}`}
          isLast={itemIdx === value.length - 1}
          defaultExpanded={false}
          depth={depth + 2}
        />
      ))}
    </div>
  );
};

Comment on lines +156 to +162
{children ?? (
<StyledWrapper className="flex items-center">
<button className="p-1">
<IconBookmark size={16} strokeWidth={2} />
</button>
</StyledWrapper>
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Nested interactive elements create accessibility issues.

When children is not provided, you have a <div role="button"> wrapping a <button>. This is problematic for screen readers and keyboard navigation. Replace the inner <button> with a non-interactive element.

         {children ?? (
           <StyledWrapper className="flex items-center">
-            <button className="p-1">
+            <span className="p-1">
               <IconBookmark size={16} strokeWidth={2} />
-            </button>
+            </span>
           </StyledWrapper>
         )}
📝 Committable suggestion

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

Suggested change
{children ?? (
<StyledWrapper className="flex items-center">
<button className="p-1">
<IconBookmark size={16} strokeWidth={2} />
</button>
</StyledWrapper>
)}
{children ?? (
<StyledWrapper className="flex items-center">
<span className="p-1">
<IconBookmark size={16} strokeWidth={2} />
</span>
</StyledWrapper>
)}
🤖 Prompt for AI Agents
In packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js
around lines 156-162, there's a nested interactive element: a div with
role="button" is wrapping an actual <button>, which harms accessibility; replace
the inner <button> with a non-interactive element (e.g., a <span> or <div>) that
preserves the visual styling (classes p-1 and IconBookmark) and mark it
aria-hidden="true" if appropriate, and ensure all keyboard/click handling
remains on the outer role="button" wrapper (no duplicate event handlers on the
inner element).

Comment on lines +34 to +42
<div role="button" tabIndex={0} onClick={clearResponse} title={!children ? 'Clear response' : null} onKeyDown={handleKeyDown}>
{children ? children : (
<StyledWrapper className="flex items-center">
<button className="p-1">
<IconEraser size={16} strokeWidth={2} />
</button>
</StyledWrapper>
)}
</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

Remove nested interactive elements.

The structure creates nested interactive elements: a div[role="button"] wrapping a <button> element. This violates accessibility best practices and creates redundant tab stops.

Apply this diff to flatten the structure:

  return (
-   <div role="button" tabIndex={0} onClick={clearResponse} title={!children ? 'Clear response' : null} onKeyDown={handleKeyDown}>
-     {children ? children : (
-       <StyledWrapper className="flex items-center">
-         <button className="p-1">
-           <IconEraser size={16} strokeWidth={2} />
-         </button>
-       </StyledWrapper>
-     )}
-   </div>
+   <div role="button" tabIndex={0} onClick={clearResponse} title={!children ? 'Clear response' : null} onKeyDown={handleKeyDown}>
+     {children ? children : (
+       <StyledWrapper className="flex items-center p-1">
+         <IconEraser size={16} strokeWidth={2} />
+       </StyledWrapper>
+     )}
+   </div>
  );
📝 Committable suggestion

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

Suggested change
<div role="button" tabIndex={0} onClick={clearResponse} title={!children ? 'Clear response' : null} onKeyDown={handleKeyDown}>
{children ? children : (
<StyledWrapper className="flex items-center">
<button className="p-1">
<IconEraser size={16} strokeWidth={2} />
</button>
</StyledWrapper>
)}
</div>
<div role="button" tabIndex={0} onClick={clearResponse} title={!children ? 'Clear response' : null} onKeyDown={handleKeyDown}>
{children ? children : (
<StyledWrapper className="flex items-center p-1">
<IconEraser size={16} strokeWidth={2} />
</StyledWrapper>
)}
</div>
🤖 Prompt for AI Agents
In packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js around
lines 34-42, the markup nests a <button> inside a div[role="button"], which
creates redundant interactive elements and duplicate tab stops; to fix, flatten
the structure by keeping a single interactive control: either convert the outer
div into a non-interactive wrapper and keep the inner <button> as the sole
control (move onClick, onKeyDown, title to the button and remove role/tabIndex
from the wrapper), or remove the inner <button> and replace it with a
non-interactive element (e.g., a span with aria-hidden) so the outer
div[role="button"] remains the only interactive element; ensure only one element
is focusable/tabbable and that keyboard handlers and accessibility attributes
are attached to that single control.

Comment on lines 52 to 66
return (
<StyledWrapper className="ml-2 flex items-center">
<button onClick={copyResponse} disabled={!response.data} title="Copy response to clipboard">
{copied ? (
<IconCheck size={16} strokeWidth={1.5} />
) : (
<IconCopy size={16} strokeWidth={1.5} />
)}
</button>
</StyledWrapper>
<div role="button" tabIndex={0} onClick={handleClick} title={!children ? 'Copy response to clipboard' : null} onKeyDown={handleKeyDown}>
{children ? children : (
<StyledWrapper className="flex items-center">
<button className="p-1" disabled={!hasData}>
{copied ? (
<IconCheck size={16} strokeWidth={2} />
) : (
<IconCopy size={16} strokeWidth={2} />
)}
</button>
</StyledWrapper>
)}
</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

Fix nested interactive elements accessibility issue.

The outer div with role="button", tabIndex={0}, onClick, and onKeyDown wraps an actual <button> element when children is not provided. This creates nested interactive elements, an accessibility anti-pattern that:

  • Creates duplicate tab stops for the same action
  • Confuses screen readers about which element to interact with
  • Violates WCAG guidelines

The outer div should only be interactive when custom children are provided. When using the default button rendering, the inner <button> should be the sole interactive element.

Apply this refactor:

+ const isCustomRender = !!children;
+
  return (
-   <div role="button" tabIndex={0} onClick={handleClick} title={!children ? 'Copy response to clipboard' : null} onKeyDown={handleKeyDown}>
+   <div 
+     role={isCustomRender ? "button" : undefined}
+     tabIndex={isCustomRender ? 0 : undefined}
+     onClick={isCustomRender ? handleClick : undefined}
+     onKeyDown={isCustomRender ? handleKeyDown : undefined}
+     title={isCustomRender ? 'Copy response to clipboard' : undefined}
+   >
-     {children ? children : (
+     {isCustomRender ? children : (
        <StyledWrapper className="flex items-center">
-         <button className="p-1" disabled={!hasData}>
+         <button 
+           className="p-1" 
+           disabled={!hasData}
+           onClick={handleClick}
+           onKeyDown={handleKeyDown}
+           title="Copy response to clipboard"
+         >
            {copied ? (
              <IconCheck size={16} strokeWidth={2} />
            ) : (
              <IconCopy size={16} strokeWidth={2} />
            )}
          </button>
        </StyledWrapper>
      )}
    </div>
  );

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

🤖 Prompt for AI Agents
In packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js around
lines 52–66, the outer div with role="button", tabIndex and click/keydown
handlers currently wraps a native <button>, creating nested interactive
elements; change rendering so the outer wrapper is only interactive when custom
children are provided: if children exist, keep the outer div as the interactive
element with role/tabIndex/onClick/onKeyDown and render children inside; if
children are not provided, render a non-interactive wrapper (no role, no
tabIndex, no handlers) and attach the click handler (and title) to the inner
native <button> (or move onClick/onKeyDown to the button) so the button is the
sole focusable/interactive element and still respects hasData/disabled and
copied state.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (7)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js (1)

53-67: Nested interactive elements issue persists.

The outer div still has tabIndex={0}, onClick, and onKeyDown regardless of whether children is provided. When rendering the default UI, you end up with a focusable wrapper containing a <button> — two tab stops for one action.

Apply this diff to ensure single interactive element:

+ const isCustomRender = !!children;
+
  return (
-   <div role={!!children ? 'button' : undefined} tabIndex={0} onClick={handleClick} title={!children ? 'Copy response to clipboard' : null} onKeyDown={handleKeyDown}>
-     {children ? children : (
+   <div
+     role={isCustomRender ? 'button' : undefined}
+     tabIndex={isCustomRender ? 0 : undefined}
+     onClick={isCustomRender ? handleClick : undefined}
+     onKeyDown={isCustomRender ? handleKeyDown : undefined}
+     title={isCustomRender ? 'Copy response to clipboard' : undefined}
+   >
+     {isCustomRender ? children : (
        <StyledWrapper className="flex items-center">
-         <button className="p-1" disabled={!hasData}>
+         <button
+           className="p-1"
+           disabled={!hasData}
+           onClick={handleClick}
+           onKeyDown={handleKeyDown}
+           title="Copy response to clipboard"
+         >
            {copied ? (
              <IconCheck size={16} strokeWidth={2} />
            ) : (
              <IconCopy size={16} strokeWidth={2} />
            )}
          </button>
        </StyledWrapper>
      )}
    </div>
  );
packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js (1)

34-44: Same nested interactive elements issue as ResponseCopy.

When rendering the default UI, the outer div[tabIndex=0] wraps an inner <button>, creating duplicate tab stops. This is the same anti-pattern flagged in the past review.

Apply similar fix as suggested for ResponseCopy:

+ const isCustomRender = !!children;
+
  return (
-   <div role={!!children ? 'button' : undefined} tabIndex={0} onClick={clearResponse} title={!children ? 'Clear response' : null} onKeyDown={handleKeyDown}>
-     {children ? children : (
-       <StyledWrapper className="flex items-center">
-         <button className="p-1">
-           <IconEraser size={16} strokeWidth={2} />
-         </button>
-       </StyledWrapper>
-     )}
-   </div>
+   <div
+     role={isCustomRender ? 'button' : undefined}
+     tabIndex={isCustomRender ? 0 : undefined}
+     onClick={isCustomRender ? clearResponse : undefined}
+     onKeyDown={isCustomRender ? handleKeyDown : undefined}
+     title={isCustomRender ? 'Clear response' : undefined}
+   >
+     {isCustomRender ? children : (
+       <StyledWrapper className="flex items-center">
+         <button
+           className="p-1"
+           onClick={clearResponse}
+           onKeyDown={handleKeyDown}
+           title="Clear response"
+         >
+           <IconEraser size={16} strokeWidth={2} />
+         </button>
+       </StyledWrapper>
+     )}
+   </div>
  );
packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js (1)

143-164: Nested interactive elements and inconsistent role assignment.

Two issues here:

  1. Nested button (duplicate): When children is not provided, you have a focusable div with onClick/tabIndex wrapping a <button>. This creates duplicate focus targets and is problematic for screen readers. Replace the inner <button> with a <span>.

  2. Inverted role logic: The role="button" is only set when children is provided (line 144), but when children is absent, the div still acts as a button (has onClick, tabIndex, onKeyDown) without the proper role.

       <div
-        role={!!children ? 'button' : undefined}
+        role="button"
         tabIndex={isDisabled ? -1 : 0}
         aria-disabled={isDisabled}
         onKeyDown={handleKeyDown}
         onClick={handleSaveClick}
         title={
           !children ? disabledMessage : (isDisabled ? disabledMessage : null)
         }
         className={classnames({
           'opacity-50 cursor-not-allowed': isDisabled
         })}
         data-testid="response-bookmark-btn"
       >
         {children ?? (
           <StyledWrapper className="flex items-center">
-            <button className="p-1">
+            <span className="p-1">
               <IconBookmark size={16} strokeWidth={2} />
-            </button>
+            </span>
           </StyledWrapper>
         )}
       </div>
packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js (1)

72-103: Fix nested interactive controls and improve accessibility of the layout toggle

The outer <div role="button"> wraps a real <button> (Line 94), which creates nested interactive elements. This is problematic for screen readers and keyboard users and was already flagged in a previous review.

You can keep the outer element as the single interactive control and make the inner element non-interactive:

-      {children ? children : (
-        <StyledWrapper className="flex items-center w-full">
-          <button className="p-1">
-            {orientation === 'horizontal' ? (
-              <IconDockToBottom />
-            ) : (
-              <IconDockToRight />
-            )}
-          </button>
-        </StyledWrapper>
-      )}
+      {children ? children : (
+        <StyledWrapper className="flex items-center w-full">
+          <span className="p-1">
+            {orientation === 'horizontal' ? (
+              <IconDockToBottom />
+            ) : (
+              <IconDockToRight />
+            )}
+          </span>
+        </StyledWrapper>
+      )}

While you're here, consider wiring up an explicit accessible label on the outer control (e.g. aria-label={title} and optionally aria-pressed={orientation === 'vertical'} if you treat it as a toggle) so screen readers get a clear description instead of relying solely on title.

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

155-170: parsererror check is dead code for HTML parsing.

As noted in past review, DOMParser with 'text/html' never produces a parsererror element - that's XML-specific behavior. This try-catch block effectively just returns true.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (1)

87-89: Missing alt attribute on image.

Screen readers need alt text for accessibility.

-      return <img src={`data:${contentType.replace(/\;(.*)/, '')};base64,${dataBuffer}`} />;
+      return <img src={`data:${contentType.replace(/\;(.*)/, '')};base64,${dataBuffer}`} alt="Response preview" />;
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (1)

165-184: Incorrect dependencies in useMemo.

previewMode uses detectedContentType but dependency array includes contentType and dataBuffer instead. Also, no explicit fallback return at the end.

   const previewMode = useMemo(() => {
     // Derive preview mode based on selected format
     if (selectedFormat === 'html') return 'preview-web';
     if (selectedFormat === 'json') return 'preview-json';
     if (selectedFormat === 'xml') return 'preview-xml';
     if (selectedFormat === 'raw') return 'preview-text';
     if (selectedFormat === 'javascript') return 'preview-web';
 
     // For base64/hex, check content type to determine binary preview type
     if (selectedFormat === 'base64' || selectedFormat === 'hex') {
       if (detectedContentType) {
         if (detectedContentType.includes('image')) return 'preview-image';
         if (detectedContentType.includes('pdf')) return 'preview-pdf';
         if (detectedContentType.includes('audio')) return 'preview-audio';
         if (detectedContentType.includes('video')) return 'preview-video';
       }
       // for all other content types, return preview-text
       return 'preview-text';
     }
-  }, [selectedFormat, contentType, dataBuffer]);
+    return 'preview-text'; // fallback for unexpected formats
+  }, [selectedFormat, detectedContentType]);
🧹 Nitpick comments (14)
packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js (1)

24-25: Consider adding a hasData guard for consistency.

Unlike ResponseCopy, this component doesn't check if there's a response before enabling the clear action. While clearing an empty response is harmless, adding a similar hasData pattern would improve UX consistency and prevent unnecessary dispatches.

-const ResponseClear = ({ collection, item, children }) => {
-  const { clearResponse } = useResponseClear(item, collection);
+const ResponseClear = ({ collection, item, children }) => {
+  const { clearResponse } = useResponseClear(item, collection);
+  const response = item.response || {};
+  const hasData = !!response.data;

Then use hasData to conditionally disable the button when rendering the default UI.

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

62-73: Redundant guard and missing toast for streaming.

The isDisabled check on line 69 is redundant since isResponseTooLarge (checked on line 62) is already part of isDisabled. Also, streaming responses silently fail without a toast, which is inconsistent with the large response case.

Consider consolidating:

-    if (isResponseTooLarge) {
-      toast.error('Response size exceeds 5MB limit. Cannot save as example.');
-      e.preventDefault();
-      e.stopPropagation();
-      return;
-    }
-
-    if (isDisabled) {
+    if (isDisabled) {
+      if (isResponseTooLarge) {
+        toast.error('Response size exceeds 5MB limit. Cannot save as example.');
+      } else if (isStreamingResponse) {
+        toast.error('Response Examples aren\'t supported in streaming responses yet.');
+      }
       e.preventDefault();
       e.stopPropagation();
       return;
     }
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/StyledWrapper.js (1)

40-74: Duplicate CSS: .xml-toggle-button and .xml-array-toggle-button are identical.

Both classes share the exact same styles. Consider consolidating into a single class or using a shared base.

-  .xml-toggle-button {
-    margin-right: 8px;
-    cursor: pointer;
-    width: 16px;
-    height: 16px;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    color: ${(props) => props.theme.codemirror.tokens.atom};
-    flex-shrink: 0;
-    border-radius: 4px;
-    transition: background-color 0.2s;
-
-    &:hover {
-      background-color: ${(props) => props.theme.console.buttonHoverBg};
-    }
-  }
-
-  .xml-array-toggle-button {
-    margin-right: 8px;
-    cursor: pointer;
-    width: 16px;
-    height: 16px;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    color: ${(props) => props.theme.codemirror.tokens.atom};
-    flex-shrink: 0;
-    border-radius: 4px;
-    transition: background-color 0.2s;
-
-    &:hover {
-      background-color: ${(props) => props.theme.console.buttonHoverBg};
-    }
-  }
+  .xml-toggle-button,
+  .xml-array-toggle-button {
+    margin-right: 8px;
+    cursor: pointer;
+    width: 16px;
+    height: 16px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: ${(props) => props.theme.codemirror.tokens.atom};
+    flex-shrink: 0;
+    border-radius: 4px;
+    transition: background-color 0.2s;
+
+    &:hover {
+      background-color: ${(props) => props.theme.console.buttonHoverBg};
+    }
+  }
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (3)

28-33: Function defined inside render.

isValidTreeData is recreated on every render. Since it's a pure utility, move it outside the component or wrap in useCallback.

+// Move outside component
+const isValidTreeData = (data) => {
+  if (data === null || data === undefined) return false;
+  if (typeof data === 'object' && !Array.isArray(data)) return true;
+  if (Array.isArray(data)) return true;
+  return false;
+};
+
 export default function XmlPreview({ data, defaultExpanded = true }) {
   // ...
-  const isValidTreeData = (data) => {
-    if (data === null || data === undefined) return false;
-    if (typeof data === 'object' && !Array.isArray(data)) return true;
-    if (Array.isArray(data)) return true;
-    return false;
-  };

219-221: Unused variables.

hasArrayValue and arrayLength are declared but never used.

-  // Determine if this node's value is an array
-  const hasArrayValue = Array.isArray(node);
-  const arrayLength = hasArrayValue ? node.length : 0;

126-143: Array index as React key.

Using key={idx} for repeated elements can cause rendering issues if the array order changes. Consider using a more stable key if available.

       {node.map((item, idx) => (
          <XmlNode
-           key={idx}
+           key={`${displayNodeName}-${idx}`}
            node={item}
packages/bruno-app/src/utils/common/index.js (1)

466-483: Comment/behavior mismatch.

Comment says "Simple HTML formatter" but uses xmlFormat. This works but may produce unexpected output for non-XHTML content (e.g., void elements without self-closing syntax).

-// Simple HTML formatter that indents HTML properly
+// Formats HTML/XML-like strings using xml-formatter with fallback
 export function prettifyHtmlString(htmlString) {
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (2)

1-1: Unused import.

useMemo is imported but not used in this component.

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

121-131: Hardcoded colors in fallback UI.

Per coding guidelines, Tailwind classes shouldn't define colors - use styled components with theme props instead.

Consider moving this to StyledWrapper with theme-aware colors:

     default:
       return (
-        <div className="p-4 flex flex-col items-center justify-center h-full text-center">
-          <div className="text-lg font-semibold text-gray-700 dark:text-gray-200 mb-2">
+        <div className="p-4 flex flex-col items-center justify-center h-full text-center no-preview-message">
+          <div className="text-lg font-semibold mb-2 title">
             No Preview Available
           </div>
-          <div className="text-sm text-gray-500 dark:text-gray-400">
+          <div className="text-sm subtitle">
             Sorry, no preview is available for this content type.
           </div>
         </div>
       );

Then add theme-aware styles in StyledWrapper. Based on learnings about using theme props for colors.

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

151-159: Unused dependency in useMemo.

responseEncoding is in the dependency array but not used in the formattedData computation.

     [data, dataBuffer, responseEncoding, selectedFormat, filter, isLargeResponse, showLargeResponse]
+    // Note: responseEncoding removed if not actually used
     [data, dataBuffer, selectedFormat, filter, isLargeResponse, showLargeResponse]
packages/bruno-app/src/ui/ButtonDropdown/index.jsx (4)

9-19: Remove redundant role and add explicit button type.

The role="button" attribute is unnecessary on a <button> element. Additionally, add type="button" to prevent unintended form submissions.

 <button
   ref={ref}
   className={classnames('button-dropdown-button flex items-center gap-1.5 text-xs',
     'cursor-pointer select-none',
     'h-7 rounded-[6px] border px-2 transition-colors',
     { 'opacity-50 cursor-not-allowed': disabled },
     className)}
   data-testid={props['data-testid']}
   style={style}
-  role="button"
+  type="button"
   {...props}
 >

48-60: Simplify unnecessary variable assignments.

The assignments const groups = options (line 50, 76) and const flatOptions = options (line 56, 104) don't add clarity or type safety. Consider using options directly.

 const findSelectedLabel = () => {
   if (isGrouped) {
-    const groups = options;
-    for (const group of groups) {
+    for (const group of options) {
       const option = group.options.find((opt) => opt.value === value);
       if (option) return option.label;
     }
   } else {
-    const flatOptions = options;
-    const option = flatOptions.find((opt) => opt.value === value);
+    const option = options.find((opt) => opt.value === value);
     if (option) return option.label;
   }
   return label;
 };

Apply similar changes to renderOptions at lines 76 and 104.


74-120: Consider extracting option rendering logic.

The renderOptions function handles both grouped and flat options in a single 46-line function. Extracting separate helper functions for each case would improve maintainability.

Example refactor:

const renderFlatOptions = () => {
  return options.map((option) => (
    <div
      key={option.value}
      className={classnames('dropdown-item flex items-center gap-2', {
        active: option.value === value
      })}
      onClick={() => handleOptionSelect(option.value)}
    >
      <span>{option.label}</span>
      {option.value === value && (
        <span className="ml-auto"></span>
      )}
    </div>
  ));
};

const renderGroupedOptions = () => {
  return options.map((group, groupIndex) => (
    <React.Fragment key={groupIndex}>
      {group.options.map((option, optionIndex) => {
        const isFirstInGroup = optionIndex === 0;
        const isFirstGroup = groupIndex === 0;
        const showSeparator = !isFirstGroup && isFirstInGroup;
        
        return (
          <div
            key={option.value}
            className={classnames('dropdown-item flex items-center gap-2', {
              'active': option.value === value,
              'border-top': showSeparator
            })}
            onClick={() => handleOptionSelect(option.value)}
          >
            <span>{option.label}</span>
            {option.value === value && (
              <span className="ml-auto"></span>
            )}
          </div>
        );
      })}
    </React.Fragment>
  ));
};

const renderOptions = () => {
  return isGrouped ? renderGroupedOptions() : renderFlatOptions();
};

130-130: Simplify data-testid spreading.

The conditional spread for data-testid can be simplified for better readability.

-<div {...(props['data-testid'] && { 'data-testid': props['data-testid'] + '-dropdown' })}>
+<div data-testid={props['data-testid'] ? `${props['data-testid']}-dropdown` : undefined}>
📜 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 dac8f7d and 1996460.

📒 Files selected for processing (15)
  • packages/bruno-app/src/components/ErrorAlert/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (4 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (4 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js (4 hunks)
  • packages/bruno-app/src/ui/ButtonDropdown/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/ui/ButtonDropdown/index.jsx (1 hunks)
  • packages/bruno-app/src/utils/common/index.js (4 hunks)
  • packages/bruno-app/src/utils/response/index.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/bruno-app/src/components/ErrorAlert/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx
🧰 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/ui/ButtonDropdown/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/StyledWrapper.js
  • packages/bruno-app/src/ui/ButtonDropdown/index.jsx
  • packages/bruno-app/src/utils/common/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js
  • packages/bruno-app/src/utils/response/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{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
Styled Components are used as wrappers to define both self and children components style; Tailwind classes are used specifically for layout based styles
Styled Component CSS might also change layout but Tailwind classes shouldn't define colors

Files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • packages/bruno-app/src/ui/ButtonDropdown/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/StyledWrapper.js
📚 Learning: 2025-12-05T20:31:32.975Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:32.975Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Add in JSDoc comments to add more details to the abstractions if needed

Applied to files:

  • packages/bruno-app/src/utils/common/index.js
📚 Learning: 2025-12-05T20:31:32.975Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:32.975Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Applied to files:

  • packages/bruno-app/src/utils/common/index.js
📚 Learning: 2025-12-02T09:45:31.698Z
Learnt from: sid-bruno
Repo: usebruno/bruno PR: 6266
File: packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js:38-38
Timestamp: 2025-12-02T09:45:31.698Z
Learning: In the ResponseCopy component (packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js), the copy button is intentionally disabled using `!response.data` to prevent copying stream resets which result in empty strings.

Applied to files:

  • packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js
🧬 Code graph analysis (11)
packages/bruno-app/src/ui/ButtonDropdown/StyledWrapper.js (2)
packages/bruno-app/src/components/ErrorAlert/StyledWrapper.js (1)
  • StyledWrapper (3-42)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/StyledWrapper.js (1)
  • StyledWrapper (3-75)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/StyledWrapper.js (2)
packages/bruno-app/src/components/ErrorAlert/StyledWrapper.js (1)
  • StyledWrapper (3-42)
packages/bruno-app/src/ui/ButtonDropdown/StyledWrapper.js (1)
  • StyledWrapper (3-22)
packages/bruno-app/src/ui/ButtonDropdown/index.jsx (3)
packages/bruno-app/src/components/Icons/IconCaretDown/index.js (1)
  • IconCaretDown (3-16)
packages/bruno-app/src/ui/ButtonDropdown/StyledWrapper.js (1)
  • StyledWrapper (3-22)
packages/bruno-app/src/components/Dropdown/index.js (1)
  • Dropdown (5-25)
packages/bruno-app/src/utils/common/index.js (1)
packages/bruno-js/src/bru.js (1)
  • xmlFormat (2-2)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js (3)
packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js (1)
  • response (39-39)
packages/bruno-app/src/components/ResponsePane/index.js (1)
  • response (67-67)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js (1)
  • StyledWrapper (3-12)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (5)
packages/bruno-app/src/utils/common/index.js (3)
  • parsed (318-318)
  • i (21-21)
  • i (515-515)
packages/bruno-app/src/components/ErrorAlert/index.js (1)
  • ErrorAlert (5-23)
packages/bruno-app/src/components/ErrorAlert/StyledWrapper.js (1)
  • StyledWrapper (3-42)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/StyledWrapper.js (1)
  • StyledWrapper (3-75)
packages/bruno-app/src/utils/response/index.js (2)
  • parser (157-157)
  • i (80-80)
packages/bruno-app/src/utils/response/index.js (2)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (2)
  • contentType (126-126)
  • buffer (118-118)
packages/bruno-app/src/utils/common/index.js (4)
  • contentType (93-93)
  • i (21-21)
  • i (515-515)
  • trimmed (537-537)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (3)
packages/bruno-app/src/utils/common/index.js (7)
  • dataBuffer (280-280)
  • dataBuffer (384-384)
  • contentType (93-93)
  • getContentType (83-119)
  • getContentType (83-119)
  • formatResponse (273-429)
  • formatResponse (273-429)
packages/bruno-app/src/utils/response/index.js (4)
  • detectContentTypeFromBuffer (178-260)
  • detectContentTypeFromBuffer (178-260)
  • getDefaultResponseFormat (8-59)
  • getDefaultResponseFormat (8-59)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (1)
  • QueryResultPreview (20-133)
packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js (3)
packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js (1)
  • useResponseLayoutToggle (44-44)
packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js (1)
  • handleKeyDown (27-32)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js (1)
  • handleKeyDown (40-45)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (4)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (1)
  • HtmlPreview (5-76)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/JsonPreview.js (1)
  • JsonPreview (5-61)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/TextPreview.js (1)
  • TextPreview (3-23)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (1)
  • XmlPreview (6-71)
packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js (1)
packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js (1)
  • StyledWrapper (3-13)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: CLI Tests
  • GitHub Check: Unit Tests
  • GitHub Check: Playwright E2E Tests
🔇 Additional comments (21)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js (2)

6-35: LGTM on hook extraction!

The useResponseCopy hook cleanly encapsulates the copy logic, state management, and proper cleanup. Good use of the hasData flag to prevent copying empty streams (per existing design intent).


40-51: Space key fix applied correctly.

The e.preventDefault() is now in place for both Enter and Space keys, preventing unwanted page scroll.

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

7-22: LGTM on hook extraction!

Clean encapsulation of the Redux dispatch logic. The hook signature is clear and the return value is appropriate.


27-32: Space key fix applied correctly.

The e.preventDefault() is now in place, addressing the previous review feedback.

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

41-52: LGTM!

Keyboard handling is correctly implemented with proper guards for the disabled state and standard Enter/Space key triggers.

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

6-49: Icon components look good and are ready for reuse

IconDockToBottom and IconDockToRight are self-contained, consistent SVG components and safe to export for reuse. No changes needed here.


53-67: Guard against undefined preferences / layout when building updatedPreferences

In toggleOrientation, you spread preferences and preferences.layout directly. If either is undefined, this will throw at runtime when the user toggles the layout. The optional chaining in preferences?.layout?.responsePaneOrientation indicates these values can be undefined.

Defensively default these to empty objects when spreading:

-    const updatedPreferences = {
-      ...preferences,
-      layout: {
-        ...preferences.layout,
-        responsePaneOrientation: newOrientation
-      }
-    };
+    const updatedPreferences = {
+      ...(preferences || {}),
+      layout: {
+        ...(preferences?.layout || {}),
+        responsePaneOrientation: newOrientation
+      }
+    };

This avoids a TypeError when either object is missing, while preserving behavior when present.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/StyledWrapper.js (1)

1-39: LGTM!

Good use of theme props for colors and codemirror tokens. The monospace font stack and styling are appropriate for code/XML display.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (2)

349-359: Case sensitivity inconsistency in nodeName handling.

Line 349 uses toLowerCase() for comparison but line 346 preserves original casing. This could cause duplicate entries if XML has mixed-case elements like <Item> and <item>.

Verify if mixed-case XML element names should be treated as distinct or merged.


1-71: LGTM on overall structure.

Good error handling with ErrorAlert, proper useMemo for parsing, and clean component composition with XmlNode/XmlArrayNode.

packages/bruno-app/src/utils/common/index.js (3)

105-116: LGTM!

Good addition of JavaScript content-type pattern matching, consistent with existing JSON/XML patterns.


325-422: LGTM on new format handlers.

Clean implementation of html, javascript, hex, base64, and text/raw format branches with appropriate large response handling and fallbacks.


530-558: Improved hex detection heuristics.

Past review concern about false positives addressed with minimum length of 6 and requiring at least one digit. Good defensive approach.

packages/bruno-app/src/utils/response/index.js (3)

1-59: LGTM!

Clean rule-based MIME type mapping with proper fallbacks. Good separation of concerns.


72-93: Guard for empty buffer added.

Line 76 now properly handles empty buffers, addressing the previous division-by-zero concern.


173-260: Solid magic number detection.

Comprehensive coverage of common binary formats (images, video, audio, archives) with appropriate fallback to text detection.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (1)

109-119: LGTM!

Clean integration of new preview components for JSON, text, and XML formats.

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

13-31: LGTM!

Well-structured format options with clear CodeMirror mode mappings.


46-105: LGTM on custom hooks.

Clean separation of concerns for initial format detection and preview option filtering. Buffer conversion error handling is appropriate.


186-190: Fallback mode corrected.

Good - now uses 'text/plain' instead of the non-standard 'application/text'.

packages/bruno-app/src/ui/ButtonDropdown/StyledWrapper.js (1)

8-14: Verify that all referenced theme properties are defined in the theme configuration.

The component uses theme.dropdown.primaryText, theme.dropdown.hoverBg, theme.dropdown.separator, and theme.workspace.border. Confirm these properties exist in your theme configuration to prevent runtime errors.

@abhishek-bruno abhishek-bruno force-pushed the feat/response-data-type-selector branch from 6b16847 to ac51080 Compare December 8, 2025 13:29
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

5-19: Harden message rendering and make the close control properly accessible

Two small improvements here:

  • JSON.stringify(message, null, 2) will throw for non-serializable objects (e.g. circular structures) and gives slightly odd output for Error instances. Consider normalizing to a string with a helper that handles Error and wraps JSON.stringify in a try/catch so the alert itself never crashes.
  • The close control is a div with onClick. For accessibility, it should be a real <button> (with type="button" and an aria-label) so it’s focusable and activatable via keyboard:
-      <div className="close-button flex-shrink-0 cursor-pointer" onClick={onClose}>
-        <IconX size={16} strokeWidth={1.5} />
-      </div>
+      <button
+        type="button"
+        className="close-button flex-shrink-0 cursor-pointer"
+        onClick={onClose}
+        aria-label="Dismiss error"
+      >
+        <IconX size={16} strokeWidth={1.5} />
+      </button>
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (1)

83-95: Make expand/collapse buttons keyboard-accessible

Both toggle controls are <button> elements but use tabIndex={-1}, which removes them from the normal tab order. That makes the XML tree hard to operate via keyboard:

<button
  onClick={toggle}
  className="xml-array-toggle-button"
- tabIndex={-1}
+ tabIndex={0} // or omit; default is focusable
  aria-expanded={expanded}
>

Same for the main node toggle button. Consider also adding an aria-label that describes what’s being expanded (e.g. including nodeName) for screen readers.

Also applies to: 224-243

📜 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 1996460 and 6b16847.

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

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

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

Files:

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

Applied to files:

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

Applied to files:

  • packages/bruno-app/src/ui/ErrorAlert/StyledWrapper.js
🧬 Code graph analysis (2)
packages/bruno-app/src/ui/ErrorAlert/index.js (1)
packages/bruno-app/src/ui/ErrorAlert/StyledWrapper.js (1)
  • StyledWrapper (3-42)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (5)
packages/bruno-app/src/utils/common/index.js (3)
  • parsed (318-318)
  • i (21-21)
  • i (515-515)
packages/bruno-app/src/ui/ErrorAlert/index.js (1)
  • ErrorAlert (5-23)
packages/bruno-app/src/ui/ErrorAlert/StyledWrapper.js (1)
  • StyledWrapper (3-42)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/StyledWrapper.js (1)
  • StyledWrapper (3-75)
packages/bruno-app/src/utils/response/index.js (2)
  • parser (157-157)
  • i (80-80)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: CLI Tests
  • GitHub Check: Unit Tests
  • GitHub Check: Playwright E2E Tests
🔇 Additional comments (3)
packages/bruno-app/src/ui/ErrorAlert/StyledWrapper.js (1)

3-41: LGTM: theming and layout responsibilities are well split

This wrapper cleanly delegates layout to Tailwind (via caller classNames) while keeping all colors and typography theme-driven in styled-components. Border/background choices and scroll bounds look consistent with the rest of the app and coding standards.
Based on learnings, styled components owning color/theming is exactly what we want here.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (2)

6-71: XML preview flow and error handling look solid

The main XmlPreview component wiring (type-checking data, useMemo for parsing, clear ErrorAlert branches, and unwrapping the single-root object) is straightforward and easy to follow. The tree rendering via XmlNode / XmlArrayNode with local useState for expansion nicely avoids any direct DOM manipulation and aligns with the previous review feedback.


155-167: Avoid rendering null XML nodes as the string "null"

xmlToObject can legitimately return null for empty elements (especially in repeated arrays), and isTextNode currently treats null as a text node, which causes empty XML nodes to render as the literal text "null" in the UI rather than displaying as empty values.

The issue flows through this path:

function isTextNode(node) {
  return typeof node === 'string' || typeof node === 'number' || node === null;
}

When a leaf node is null, the code calls String(node), converting it to the string "null":

if (isLeaf && isTextNode(node)) {
  const value = String(node); // null → "null"
  // ...
}

To fix this, remove node === null from isTextNode() so null leaves render through the empty‑leaf branch with proper empty value styling:

-function isTextNode(node) {
-  return typeof node === 'string' || typeof node === 'number' || node === null;
-}
+function isTextNode(node) {
+  return typeof node === 'string' || typeof node === 'number';
+}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js (1)

17-25: Remove unnecessary key prop.

The key prop on line 24 is unnecessary here. React's key is only meaningful when rendering lists/arrays to help with reconciliation. Since QueryResponse is a single element (not mapped from an array), this prop serves no purpose and should be removed.

Apply this diff:

             <div className="h-96 overflow-auto">
               <QueryResponse
                 item={item}
                 collection={collection}
                 data={data}
                 dataBuffer={dataBuffer}
                 headers={headers}
                 error={error}
-                key={item?.uid}
               />
             </div>
♻️ Duplicate comments (11)
packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js (1)

157-163: Nested interactive elements create accessibility issues.

When children is not provided, the fallback UI wraps a <button> element (line 159) inside a <div> that handles all the click and keyboard events (lines 147-148). This nested structure is problematic for screen readers and keyboard navigation, as previously noted.

Replace the inner <button> with a non-interactive element:

         {children ?? (
           <StyledWrapper className="flex items-center">
-            <button className="p-1">
+            <span className="p-1">
               <IconBookmark size={16} strokeWidth={2} />
-            </button>
+            </span>
           </StyledWrapper>
         )}
packages/bruno-app/src/utils/response/index.js (1)

155-170: parsererror check is dead code for HTML parsing.

DOMParser with 'text/html' is lenient and never generates a parsererror element — that behavior is XML-specific. This check and try-catch are effectively unreachable code.

   try {
-    // Parse with DOMParser
     const parser = new DOMParser();
     const doc = parser.parseFromString(trimmed, 'text/html');
-
-    // Check for parsing errors
-    const parseError = doc.querySelector('parsererror');
-    if (parseError) {
-      return false;
-    }
-
-    // HTML parser is lenient; if we reach here with valid tags, consider it valid
     return true;
-  } catch (error) {
-    return false;
-  }
+  } catch {
+    return false; // Defensive, though unlikely to throw
+  }
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (1)

345-359: Case-sensitivity bug remains unaddressed.

The comparison at line 349 compares c.nodeName.toLowerCase() against childName which preserves original casing. Tags like <Item></Item> won't match ('item' === 'Item' is false), causing empty repeated elements to be dropped.

       elementChildren.forEach((child) => {
         const childName = child.nodeName; // Preserve original casing
         const childValue = xmlToObject(child);
-
-        if (childValue !== null || elementChildren.filter((c) => c.nodeName.toLowerCase() === childName).length > 1) {
+        const sameNameCount = elementChildren.filter(
+          (c) => c.nodeName.toLowerCase() === childName.toLowerCase()
+        ).length;
+
+        if (childValue !== null || sameNameCount > 1) {
packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js (1)

85-103: Nested interactive elements still present.

The outer <div role="button"> wraps an inner <button> (line 94), creating nested interactive elements. This was flagged in a prior review. Screen readers and keyboard navigation will have issues with this pattern.

Consider the previously suggested fix:

       {children ? children : (
         <StyledWrapper className="flex items-center w-full">
-          <button className="p-1">
+          <span className="p-1">
             {orientation === 'horizontal' ? (
               <IconDockToBottom />
             ) : (
               <IconDockToRight />
             )}
-          </button>
+          </span>
         </StyledWrapper>
       )}
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (1)

87-89: Missing alt attribute on image.

Accessibility requires an alt attribute on <img> elements.

-      return <img src={`data:${contentType.replace(/\;(.*)/, '')};base64,${dataBuffer}`} />;
+      return <img src={`data:${contentType.replace(/\;(.*)/, '')};base64,${dataBuffer}`} alt="Response preview" />;
packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js (1)

42-60: Fix nested interactive elements and inconsistent accessibility attributes.

The wrapper div has inconsistent interactive attributes and creates nested interactive elements when no custom children are provided:

  • tabIndex={0} makes it focusable even when role is undefined
  • When children are not provided, the outer div wraps a <button> element, creating duplicate tab stops

This is the same accessibility issue flagged in ResponseCopy and ResponseClear.

Apply this fix:

+ const isCustomRender = !!children;
+
  return (
    <div
-     role={!!children ? 'button' : undefined}
-     tabIndex={isDisabled ? -1 : 0}
+     role={isCustomRender ? 'button' : undefined}
+     tabIndex={isCustomRender && !isDisabled ? 0 : undefined}
      aria-disabled={isDisabled}
-     onClick={saveResponseToFile}
-     onKeyDown={handleKeyDown}
+     onClick={isCustomRender ? saveResponseToFile : undefined}
+     onKeyDown={isCustomRender ? handleKeyDown : undefined}
      title={!children ? 'Save response to file' : null}
      className={classnames({
        'opacity-50 cursor-not-allowed': isDisabled
      })}
    >
-     {children ? children : (
+     {isCustomRender ? children : (
        <StyledWrapper className="flex items-center">
-         <button className="p-1">
+         <button 
+           className="p-1" 
+           onClick={saveResponseToFile}
+           onKeyDown={handleKeyDown}
+           disabled={isDisabled}
+           title="Save response to file"
+         >
            <IconDownload size={16} strokeWidth={2} />
          </button>
        </StyledWrapper>
      )}
    </div>
  );

Based on learnings, the disabled check using !response.dataBuffer is intentional to prevent saving incomplete stream data.

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

54-66: Nested interactive elements issue remains unresolved.

The accessibility issue flagged in the previous review is still present. The outer div has tabIndex={0} for all cases but role="button" only when children exist, creating inconsistent behavior. When no children are provided, the outer div wraps a <button> element, resulting in nested interactive elements.

Apply the previously suggested fix:

+ const isCustomRender = !!children;
+
  return (
-   <div role={!!children ? 'button' : undefined} tabIndex={0} onClick={handleClick} title={!children ? 'Copy response to clipboard' : null} onKeyDown={handleKeyDown}>
-     {children ? children : (
+   <div 
+     role={isCustomRender ? 'button' : undefined}
+     tabIndex={isCustomRender ? 0 : undefined}
+     onClick={isCustomRender ? handleClick : undefined}
+     onKeyDown={isCustomRender ? handleKeyDown : undefined}
+     title={isCustomRender ? 'Copy response to clipboard' : undefined}
+   >
+     {isCustomRender ? children : (
        <StyledWrapper className="flex items-center">
-         <button className="p-1" disabled={!hasData}>
+         <button 
+           className="p-1" 
+           disabled={!hasData}
+           onClick={handleClick}
+           onKeyDown={handleKeyDown}
+           title="Copy response to clipboard"
+         >
            {copied ? (
              <IconCheck size={16} strokeWidth={2} />
            ) : (
              <IconCopy size={16} strokeWidth={2} />
            )}
          </button>
        </StyledWrapper>
      )}
    </div>
  );
packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js (1)

35-43: Nested interactive elements issue remains unresolved.

The accessibility issue flagged in the previous review is still present. The structure nests a <button> inside a div with tabIndex={0}, creating duplicate tab stops and an accessibility anti-pattern.

Apply the previously suggested fix:

+ const isCustomRender = !!children;
+
  return (
-   <div role={!!children ? 'button' : undefined} tabIndex={0} onClick={clearResponse} title={!children ? 'Clear response' : null} onKeyDown={handleKeyDown}>
-     {children ? children : (
+   <div 
+     role={isCustomRender ? 'button' : undefined}
+     tabIndex={isCustomRender ? 0 : undefined}
+     onClick={isCustomRender ? clearResponse : undefined}
+     onKeyDown={isCustomRender ? handleKeyDown : undefined}
+     title={isCustomRender ? 'Clear response' : undefined}
+   >
+     {isCustomRender ? children : (
        <StyledWrapper className="flex items-center">
-         <button className="p-1">
+         <button 
+           className="p-1"
+           onClick={clearResponse}
+           onKeyDown={handleKeyDown}
+           title="Clear response"
+         >
            <IconEraser size={16} strokeWidth={2} />
          </button>
        </StyledWrapper>
      )}
    </div>
  );
packages/bruno-app/src/ui/ButtonDropdown/index.jsx (2)

2-2: Use custom IconCaretDown component for consistency.

The codebase has a custom IconCaretDown at components/Icons/IconCaretDown/index.js. Import that instead of the external @tabler/icons package to maintain consistency.

-import { IconCaretDown } from '@tabler/icons';
+import IconCaretDown from 'components/Icons/IconCaretDown';

Based on past review comments.


69-71: Close dropdown after selection.

The dropdown remains open after an option is selected, requiring users to click outside to dismiss it.

 const handleOptionSelect = (optionValue) => {
   onChange(optionValue);
+  dropdownTippyRef.current?.hide();
 };

Based on past review comments.

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

546-565: switchToEditorTab is still identical to switchToPreviewTab and likely incorrect.

Both helpers click the same preview-response-tab control, so switchToEditorTab does not guarantee ending up in the editor view and can leave tests in the wrong state (or toggle preview back on).

If the preview is a toggle, switchToEditorTab should only click when preview is currently on. For example:

const switchToEditorTab = async (page: Page) => {
  await test.step('Switch to editor tab', async () => {
    const responseFormatTab = page.getByTestId('format-response-tab');
    await responseFormatTab.click();
    const previewTab = page.getByTestId('preview-response-tab');
-    await previewTab.click();
+    const isPreviewOn = await previewTab.isChecked();
+    if (isPreviewOn) {
+      await previewTab.click();
+    }
  });
};

Adjust the state check if the underlying control is not checkable, but the key point is: switchToEditorTab should reliably land in editor mode, not mirror switchToPreviewTab.

🧹 Nitpick comments (13)
packages/bruno-app/src/ui/ErrorAlert/StyledWrapper.js (1)

13-13: Avoid hardcoded hex comparison for theme detection.

Comparing against #1e1e1e is fragile and couples this component to a specific theme value. If the dark theme background changes, this logic breaks silently. Based on learnings, use the theme prop to manage colors rather than hardcoded checks.

Consider using a dedicated theme property (e.g., props.theme.isDark or similar) or define the background color directly in your theme:

-  background-color: ${(props) => (props.theme.bg === '#1e1e1e' ? 'rgba(40, 40, 40, 0.5)' : 'rgba(250, 250, 250, 0.9)')};
+  background-color: ${(props) => props.theme.colors.bg.danger};

Or if a semantic token doesn't exist yet, add one to the theme definition and reference it here.

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

54-76: Consider consolidating disabled checks.

The function has two overlapping checks:

  • Line 62 checks isResponseTooLarge (part of isDisabled)
  • Line 69 checks the full isDisabled condition

Since isDisabled = isResponseTooLarge || isStreamingResponse, and line 62 handles the size case with a specific error message, line 69 effectively only guards against isStreamingResponse. Consider either:

  1. Moving the streaming-specific error message to line 69 for clarity, or
  2. Consolidating to a single check with conditional error messages

This improves code maintainability.

Example refactor:

-    if (isResponseTooLarge) {
-      toast.error('Response size exceeds 5MB limit. Cannot save as example.');
-      e.preventDefault();
-      e.stopPropagation();
-      return;
-    }
-
     if (isDisabled) {
+      const errorMessage = isResponseTooLarge
+        ? 'Response size exceeds 5MB limit. Cannot save as example.'
+        : 'Response Examples aren\'t supported in streaming responses yet.';
+      toast.error(errorMessage);
       e.preventDefault();
       e.stopPropagation();
       return;
     }
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (2)

1-2: Import order nit.

React import should typically come first before other imports for consistency.

-import ErrorAlert from 'ui/ErrorAlert/index';
-import React, { useState, useMemo } from 'react';
+import React, { useState, useMemo } from 'react';
+import ErrorAlert from 'ui/ErrorAlert/index';

130-132: Consider more stable key for array mapping.

Using index alone as key can cause reconciliation issues if the array order changes. Since this is read-only preview, it's acceptable but combining with the node name would be safer:

-          <XmlNode
-            key={idx}
+          <XmlNode
+            key={`${displayNodeName}-${idx}`}
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/JsonPreview.js (1)

5-25: Consider moving validateJsonData outside the component.

This helper is pure and doesn't depend on component state or props. Defining it inside recreates the function on every render.

+// Helper function to validate and parse JSON data
+const validateJsonData = (data) => {
+  if (typeof data === 'object' && data !== null) {
+    return { data, error: null };
+  }
+
+  if (typeof data === 'string') {
+    try {
+      const parsed = JSON.parse(data);
+      return { data: parsed, error: null };
+    } catch (e) {
+      return { data: null, error: `Invalid JSON format: ${e.message}` };
+    }
+  }
+
+  return { data: null, error: 'Invalid input. Expected a JSON object, array, or valid JSON string.' };
+};
+
 const JsonPreview = ({ data, displayedTheme }) => {
-  // Helper function to validate and parse JSON data
-  const validateJsonData = (data) => {
-    // If data is already an object or array, use it directly
-    if (typeof data === 'object' && data !== null) {
-      return { data, error: null };
-    }
-    ...
-  };
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (1)

1-1: Unused import: useMemo.

useMemo is imported but not used in this file.

-import React, { useState, useMemo } from 'react';
+import React, { useState } from 'react';
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (2)

46-68: Duplicated buffer conversion logic across hooks.

Both useInitialResponseFormat and useResponsePreviewFormatOptions have identical try-catch blocks for converting dataBuffer to a Buffer. Consider extracting this to a shared helper.

// Could extract to a helper at the top of the file
const safeBufferFromBase64 = (dataBuffer) => {
  try {
    return dataBuffer ? Buffer.from(dataBuffer, 'base64') : null;
  } catch (error) {
    console.error('Error converting dataBuffer to Buffer:', error);
    return null;
  }
};

Also applies to: 70-105


151-159: Unused dependency: responseEncoding.

responseEncoding is in the dependency array but not used in the formatResponse call.

   const formattedData = useMemo(
     () => {
       if (isLargeResponse && !showLargeResponse) {
         return '';
       }
       return formatResponse(data, dataBuffer, selectedFormat, filter);
     },
-    [data, dataBuffer, responseEncoding, selectedFormat, filter, isLargeResponse, showLargeResponse]
+    [data, dataBuffer, selectedFormat, filter, isLargeResponse, showLargeResponse]
   );
packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js (1)

28-32: Consider removing stopPropagation unless required.

The e.stopPropagation() in the disabled state prevents event bubbling. Unless there's a specific need to stop propagation (e.g., nested components that should not handle the event), this may be unnecessary.

If not needed:

  const handleKeyDown = (e) => {
    if (isDisabled) {
      e.preventDefault();
-     e.stopPropagation();
      return;
    }
packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js (1)

51-80: LGTM with one observation.

The space checking logic correctly measures the container and siblings to determine whether to show the dropdown menu. The use of individualButtonsWidthRef to cache the buttons' width is a good optimization.

One minor note: Line 74 calculates shouldShowMenu using a strict less-than comparison. Consider whether equality should also trigger the menu (i.e., <=) to handle exact-fit edge cases where content might wrap or overflow.

packages/bruno-app/src/ui/ButtonDropdown/index.jsx (1)

18-18: Remove redundant role attribute.

The role="button" is unnecessary since <button> elements already have the button role implicitly.

-      role="button"
tests/utils/page/actions.ts (2)

472-494: Tidy up openRequest helper and consider re-adding an assertion.

The new collection-scoped openRequest makes sense for disambiguating requests within a collection, but:

  • The old implementation is now commented out; leaving it in the file adds noise and can confuse future readers.
  • The new version no longer asserts that the active tab matches requestName, which the old helper did via locators.tabs.activeRequestTab().

Consider:

  • Removing the commented-out implementation once you’re confident all callers are migrated.
  • Reintroducing an assertion that the opened tab matches requestName to catch navigation issues early.

534-540: Optional: reuse common response locators inside switchResponseFormat.

Implementation is straightforward, but you could avoid duplicating selectors by using buildCommonLocators(page).response.formatTab() and .formatTabDropdown() instead of hardcoding the test IDs here. That keeps all selector definitions centralized in locators.ts.

📜 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 6b16847 and ac51080.

📒 Files selected for processing (59)
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/StyledWrapper.js (2 hunks)
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js (0 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/JsonPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/TextPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/VideoPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (4 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (4 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js (4 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseSave/StyledWrapper.js (0 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js (0 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/StatusCode/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/StatusCode/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/index.js (5 hunks)
  • packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js (2 hunks)
  • packages/bruno-app/src/ui/ButtonDropdown/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/ui/ButtonDropdown/index.jsx (1 hunks)
  • packages/bruno-app/src/ui/ErrorAlert/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/ui/ErrorAlert/index.js (1 hunks)
  • packages/bruno-app/src/utils/common/codemirror.js (1 hunks)
  • packages/bruno-app/src/utils/common/index.js (4 hunks)
  • packages/bruno-app/src/utils/response/index.js (1 hunks)
  • tests/response/large-response-crash-prevention.spec.ts (1 hunks)
  • tests/response/response-format-select-and-preview/fixtures/collection/bruno.json (1 hunks)
  • tests/response/response-format-select-and-preview/fixtures/collection/request-html.bru (1 hunks)
  • tests/response/response-format-select-and-preview/fixtures/collection/request-json.bru (1 hunks)
  • tests/response/response-format-select-and-preview/init-user-data/collection-security.json (1 hunks)
  • tests/response/response-format-select-and-preview/init-user-data/preferences.json (1 hunks)
  • tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts (1 hunks)
  • tests/utils/page/actions.ts (3 hunks)
  • tests/utils/page/locators.ts (2 hunks)
💤 Files with no reviewable changes (3)
  • packages/bruno-app/src/components/ResponsePane/ResponseSave/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js
  • packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js
🚧 Files skipped from review as they are similar to previous changes (31)
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/StyledWrapper.js
  • tests/response/response-format-select-and-preview/init-user-data/collection-security.json
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/StyledWrapper.js
  • tests/response/response-format-select-and-preview/init-user-data/preferences.json
  • packages/bruno-app/src/utils/common/codemirror.js
  • tests/response/response-format-select-and-preview/fixtures/collection/request-html.bru
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js
  • packages/bruno-app/src/ui/ErrorAlert/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/VideoPreview.js
  • packages/bruno-app/src/components/ResponsePane/StatusCode/StyledWrapper.js
  • tests/response/response-format-select-and-preview/fixtures/collection/bruno.json
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js
  • packages/bruno-app/src/utils/common/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/TextPreview.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/StyledWrapper.js
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/index.js
  • tests/response/response-format-select-and-preview/fixtures/collection/request-json.bru
  • packages/bruno-app/src/components/ResponsePane/StatusCode/index.js
  • packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js
  • tests/response/large-response-crash-prevention.spec.ts
🧰 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. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-app/src/ui/ButtonDropdown/index.jsx
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/JsonPreview.js
  • packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/StyledWrapper.js
  • tests/utils/page/actions.ts
  • packages/bruno-app/src/utils/response/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/index.js
  • tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts
  • packages/bruno-app/src/ui/ErrorAlert/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js
  • packages/bruno-app/src/components/ResponsePane/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js
  • packages/bruno-app/src/ui/ButtonDropdown/StyledWrapper.js
  • tests/utils/page/locators.ts
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{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
Styled Components are used as wrappers to define both self and children components style; Tailwind classes are used specifically for layout based styles
Styled Component CSS might also change layout but Tailwind classes shouldn't define colors

Files:

  • packages/bruno-app/src/ui/ButtonDropdown/index.jsx
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/utils/page/actions.ts
  • tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts
  • tests/utils/page/locators.ts
🧠 Learnings (5)
📚 Learning: 2025-12-02T09:45:31.709Z
Learnt from: sid-bruno
Repo: usebruno/bruno PR: 6266
File: packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js:38-38
Timestamp: 2025-12-02T09:45:31.709Z
Learning: In the ResponseCopy component (packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js), the copy button is intentionally disabled using `!response.data` to prevent copying stream resets which result in empty strings.

Applied to files:

  • packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{jsx,tsx} : 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/ResponsePane/ResponseTime/StyledWrapper.js
  • packages/bruno-app/src/ui/ErrorAlert/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/StyledWrapper.js
  • packages/bruno-app/src/ui/ButtonDropdown/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/ResponsePane/ResponseTime/StyledWrapper.js
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/StyledWrapper.js
  • packages/bruno-app/src/ui/ErrorAlert/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/StyledWrapper.js
  • packages/bruno-app/src/ui/ButtonDropdown/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/Devtools/Console/RequestDetailsPanel/StyledWrapper.js
  • packages/bruno-app/src/ui/ErrorAlert/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/StyledWrapper.js
  • packages/bruno-app/src/ui/ButtonDropdown/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 **/*.test.{js,jsx,ts,tsx} : Add tests for any new functionality or meaningful changes. If code is added, removed, or significantly modified, corresponding tests should be updated or created

Applied to files:

  • tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts
🧬 Code graph analysis (10)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/JsonPreview.js (2)
packages/bruno-app/src/ui/ErrorAlert/index.js (1)
  • ErrorAlert (5-23)
packages/bruno-app/src/providers/Theme/index.js (1)
  • displayedTheme (11-11)
packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js (1)
packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js (1)
  • QueryResponse (8-58)
packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js (3)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js (1)
  • handleKeyDown (40-45)
packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js (1)
  • handleKeyDown (75-80)
packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js (1)
  • StyledWrapper (3-12)
packages/bruno-app/src/utils/response/index.js (3)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (1)
  • contentType (126-126)
packages/bruno-app/src/utils/common/index.js (4)
  • contentType (93-93)
  • i (21-21)
  • i (515-515)
  • trimmed (537-537)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (1)
  • parser (302-302)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (2)
packages/bruno-app/src/utils/common/index.js (7)
  • dataBuffer (280-280)
  • dataBuffer (384-384)
  • contentType (93-93)
  • getContentType (83-119)
  • getContentType (83-119)
  • formatResponse (273-429)
  • formatResponse (273-429)
packages/bruno-app/src/utils/response/index.js (4)
  • detectContentTypeFromBuffer (178-260)
  • detectContentTypeFromBuffer (178-260)
  • getDefaultResponseFormat (8-59)
  • getDefaultResponseFormat (8-59)
tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts (2)
tests/utils/page/actions.ts (6)
  • closeAllCollections (593-593)
  • openRequest (609-609)
  • sendRequestAndWaitForResponse (613-613)
  • switchToPreviewTab (615-615)
  • switchToEditorTab (616-616)
  • switchResponseFormat (614-614)
packages/bruno-app/src/utils/codemirror/brunoVarInfo.js (1)
  • editorContainer (298-298)
packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js (1)
packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js (1)
  • StyledWrapper (3-13)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js (3)
packages/bruno-app/src/components/ResponsePane/index.js (1)
  • response (67-67)
packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js (1)
  • handleKeyDown (27-32)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js (1)
  • StyledWrapper (3-12)
packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js (1)
packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js (1)
  • QueryResponse (8-58)
packages/bruno-app/src/ui/ButtonDropdown/StyledWrapper.js (1)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/StyledWrapper.js (1)
  • StyledWrapper (3-75)
🪛 GitHub Check: Unit Tests
tests/utils/page/locators.ts

[failure] 88-88:
Unexpected trailing comma

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

3-7: Typography update looks good and consistent

The font-weight bump to 600 is a safe, purely visual change, keeps using theme-based typography and colors, and matches the documented styling patterns for wrappers.

packages/bruno-app/src/ui/ErrorAlert/StyledWrapper.js (3)

1-2: LGTM!

Clean import statement.


15-26: LGTM!

Clean close-button styling with appropriate hover transition and theme-based icon color.


28-41: LGTM!

Error title and message styles are well-structured, using theme tokens for colors and font sizing as expected.

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

22-22: LGTM! Styling adjustment aligns with UI consistency goals.

The reduced left margin (ml-4 → ml-2) tightens the spacing and matches the broader ResponsePane layout updates in this PR.

packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js (2)

1-1: LGTM! Import updated to use new QueryResponse wrapper.

The import change aligns with the PR's objective to add response format selection capabilities.


17-25: Verify if disableRunEventListener prop should be passed to QueryResponse.

The QueryResponse component may accept a disableRunEventListener prop based on its signature. Confirm whether this prop was previously passed and if its omission here is intentional or an oversight during the refactor.

packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js (2)

6-11: LGTM! Clean hover interaction.

The border-radius and hover state styling are well-implemented with appropriate theme tokens for an interactive button control.


5-5: Verify semantic appropriateness of CodeMirror color token for UI button.

Using theme.codemirror.variable.info.iconColor for a ResponsePane UI control may be semantically misaligned if CodeMirror tokens are reserved for editor syntax highlighting. The hover state correctly uses theme.workspace.button.bg, suggesting the default color should also come from a UI-specific token. Consider verifying the theme structure and checking if a more appropriate token exists (e.g., theme.text, theme.icon, or a workspace-specific variant).

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

6-12: LGTM! Styling enhancements follow theme conventions.

The color migration to theme.codemirror.variable.info.iconColor, border-radius, and hover state provide better visual consistency and interactive feedback. Theme props are correctly used per coding standards.

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

27-27: LGTM! Optional children prop adds flexibility.

The signature change supports custom rendering while maintaining backward compatibility.


34-34: LGTM! Disabled state logic is clear and consistent.

The derived state correctly prevents actions for oversized or streaming responses.


41-52: LGTM! Keyboard handling enhances accessibility.

The function correctly guards against disabled state and supports standard button activation keys (Enter/Space).

packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/StyledWrapper.js (1)

171-171: LGTM — Whitespace-only adjustments.

These lines contain minor formatting tweaks with no functional impact. The file correctly uses theme props for colors as per coding standards.

Also applies to: 281-281

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (1)

73-112: Good refactor using React state for expansion.

The XmlArrayNode component properly uses useState for expansion control instead of direct DOM manipulation. This addresses the previous review concern.

packages/bruno-app/src/utils/response/index.js (4)

8-59: Clean MIME-based format detection.

The rule-based approach with regex tests is maintainable and extensible. Good coverage of common content types with sensible defaults.


62-70: LGTM — Standard HTML escaping.

Correctly escapes all necessary HTML special characters for safe webview content injection.


75-93: Division guard added — looks good.

The empty buffer check at line 76 properly addresses the potential division-by-zero issue.


216-223: MP4/WebM detection may have edge cases.

The MP4 detection checks for ftyp box with specific size bytes (0x18 or 0x20), which works for common MP4s but may miss variants with different box sizes. Similarly, the EBML signature (0x1A45DFA3) matches both WebM and MKV containers.

For this use case (preview type selection), these edge cases are acceptable — just noting for awareness.

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

36-41: LGTM — Separator styling added correctly.

Uses theme prop for the border color as per coding standards. Layout properties appropriately defined in styled component.

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

4-4: Component import updated correctly.

Clean swap from QueryResult to QueryResponse aligning with the PR's refactoring.


42-52: Remove the unused width prop if not referenced in QueryResponse.

The width={rightPaneWidth} prop passed on line 45 should be verified in the QueryResponse component definition. If the component doesn't destructure or use this prop, remove it to keep the interface clean and reduce unnecessary prop passing.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/JsonPreview.js (1)

44-60: LGTM!

Clean ReactJson configuration with appropriate theme selection and styling. The collapsed depth of 1 is sensible for large responses.

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

51-70: LGTM!

The useResponseLayoutToggle hook cleanly encapsulates orientation state and toggle logic, making it reusable across components.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (2)

64-80: LGTM!

Clean separation of editor vs preview rendering. The CodeEditor receives all necessary props for proper functionality.


109-119: LGTM!

New preview modes for JSON, text, and XML are well-integrated with their respective components.

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

13-31: LGTM!

Clean structure for PREVIEW_FORMAT_OPTIONS with appropriate CodeMirror mode mappings. The grouped structure supports future UI enhancements like optgroup rendering.


107-130: LGTM!

Component signature is clear with well-documented props. Buffer conversion error handling is appropriately defensive.

packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js (4)

1-11: LGTM!

Imports are well-organized and follow the coding guidelines. The component structure with hooks and styled components is appropriate for this use case.


126-173: Potential click handler conflict in dropdown items.

Each dropdown item has onClick={closeDropdown} on the wrapper div, but the action components (ResponseCopy, ResponseDownload, ResponseClear, ResponseLayoutToggle) also have their own click handlers. This could lead to:

  • Race conditions in event handling
  • Both handlers firing if not properly managed
  • Unexpected behavior if the action component's handler prevents event propagation

Verify that the action components properly handle being wrapped and that both click handlers execute as intended.

Consider explicitly passing an onComplete callback to action components instead:

- <ResponseCopy item={item}>
-   <div className="dropdown-item" onClick={closeDropdown}>
+ <ResponseCopy item={item} onComplete={closeDropdown}>
+   <div className="dropdown-item">

176-182: LGTM!

The individual action buttons rendering is clean and straightforward. The gap-[2px] spacing is consistent with the UI design.


91-104: Review item dependency for stability—consider depending on a stable identifier instead.

The useEffect includes item in its dependency array, which causes the ResizeObserver setup/teardown to re-run whenever the item object reference changes. If item is recreated on each render in the parent component, this will trigger unnecessary effect re-runs, setup, and cleanup operations. Consider whether depending on a stable primitive (e.g., item.id) would be sufficient instead of the entire object, or memoize item in the parent component to prevent reference instability.

packages/bruno-app/src/ui/ButtonDropdown/StyledWrapper.js (1)

1-24: LGTM! Styling follows guidelines correctly.

The styled wrapper properly uses theme props for all colors and follows the project's styling conventions.

packages/bruno-app/src/ui/ButtonDropdown/index.jsx (2)

74-142: Render logic is well-structured.

The component correctly handles both grouped and flat options, maintains proper state, and propagates test IDs appropriately. The implementation is clean and follows React best practices.


48-61: Verify fallback when no option matches.

If value doesn't match any option and label prop is undefined, this function returns undefined, which could display as "undefined" in the UI. Confirm whether the label prop has a default value or type guard, and check actual component usage to determine if this scenario occurs in practice.

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

99-100: Import locators for file input and env option look appropriate.

The new fileInput and envOption locators under import are consistent with existing patterns and should make the import flow tests clearer and less brittle.

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

516-527: sendRequestAndWaitForResponse helper is clear and matches test usage.

The helper wraps send + status-code assertion with configurable options and a sensible default timeout, which keeps the new response-format tests concise.


612-616: Exporting the new helpers keeps response-format tests DRY.

Adding sendRequestAndWaitForResponse, switchResponseFormat, switchToPreviewTab, and switchToEditorTab to the exports makes them reusable across specs and matches how the new response-format tests consume them.

tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts (1)

1-161: Well-structured E2E coverage for response format & preview flows.

The spec:

  • Uses shared helpers (openRequest, sendRequestAndWaitForResponse, format/preview switches) and buildCommonLocators to keep selectors centralized.
  • Wraps logical chunks in test.step, uses locator variables, and avoids waitForTimeout/page.pause.
  • Adds comprehensive assertions for default views plus JSON/HTML/XML/JavaScript/Raw/Hex/Base64 editor and preview behaviour.

Once the switchToEditorTab helper is fixed, these scenarios should give solid end-to-end coverage for the new feature.

Based on learnings, this satisfies the “add tests for new functionality” expectation.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
tests/utils/page/actions.ts (1)

555-566: Copy-paste bug: switchToEditorTab is identical to switchToPreviewTab.

Both functions click preview-response-tab. This was flagged in a previous review but remains unfixed. The editor function should target a different test ID or toggle off the preview if that's the intended behavior.

 const switchToEditorTab = async (page: Page) => {
   await test.step('Switch to editor tab', async () => {
     const responseFormatTab = page.getByTestId('format-response-tab');
     await responseFormatTab.click();
-    const previewTab = page.getByTestId('preview-response-tab');
-    await previewTab.click();
+    const editorTab = page.getByTestId('editor-response-tab');
+    await editorTab.click();
   });
 };

Note: Verify the correct test ID matches your implementation (e.g., editor-response-tab or similar).

packages/bruno-app/src/ui/ButtonDropdown/index.jsx (1)

2-5: Use shared IconCaretDown component instead of Tabler icon

The codebase already exposes a shared IconCaretDown at components/Icons/IconCaretDown; using it keeps icon styling and behavior consistent across the app.

-import { IconCaretDown } from '@tabler/icons';
+import IconCaretDown from 'components/Icons/IconCaretDown';
🧹 Nitpick comments (3)
tests/utils/page/actions.ts (2)

472-478: Remove commented code.

The old implementation is preserved in version control; keeping it commented adds clutter.

-// const openRequest = async (page: Page, requestName: string) => {
-//   await test.step(`Open request "${requestName}"`, async () => {
-//     const locators = buildCommonLocators(page);
-//     await locators.sidebar.request(requestName).click();
-//     await expect(locators.tabs.activeRequestTab()).toContainText(requestName);
-//   });
-// };
-

516-527: Consider consolidating with existing sendRequest function.

sendRequestAndWaitForResponse duplicates much of the logic from sendRequest (lines 448-464). Consider merging them or deprecating one to reduce maintenance burden.

packages/bruno-app/src/ui/ButtonDropdown/index.jsx (1)

45-61: Defensively handle non-array or missing options to avoid runtime errors

Both findSelectedLabel and renderOptions assume options is an array in the flat (non-grouped) case. If options is ever undefined or a non-array, calling .find/.map will throw. Since this is a generic UI building block, it’s worth hardening:

-  const isGrouped = Array.isArray(options) && options.length > 0 && 'options' in options[0];
+  const isGrouped = Array.isArray(options) && options.length > 0 && 'options' in options[0];

   // Find the selected option's label
   const findSelectedLabel = () => {
     if (isGrouped) {
-      const groups = options;
+      const groups = Array.isArray(options) ? options : [];
       for (const group of groups) {
         const option = group.options.find((opt) => opt.value === value);
         if (option) return option.label;
       }
     } else {
-      const flatOptions = options;
+      const flatOptions = Array.isArray(options) ? options : [];
       const option = flatOptions.find((opt) => opt.value === value);
       if (option) return option.label;
     }
     return label;
   };
   const renderOptions = () => {
     if (isGrouped) {
-      const groups = options;
+      const groups = Array.isArray(options) ? options : [];
       return groups.map((group, groupIndex) => (
@@
     } else {
-      const flatOptions = options;
+      const flatOptions = Array.isArray(options) ? options : [];
       return flatOptions.map((option) => (

Also applies to: 75-121

📜 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 ac51080 and 1bfacfd.

📒 Files selected for processing (8)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx (1 hunks)
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js (1 hunks)
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js (1 hunks)
  • packages/bruno-app/src/ui/ButtonDropdown/index.jsx (1 hunks)
  • tests/response/response-actions.spec.ts (1 hunks)
  • tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts (1 hunks)
  • tests/utils/page/actions.ts (3 hunks)
  • tests/utils/page/locators.ts (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx
  • tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts
  • tests/utils/page/locators.ts
🧰 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. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js
  • tests/response/response-actions.spec.ts
  • tests/utils/page/actions.ts
  • packages/bruno-app/src/ui/ButtonDropdown/index.jsx
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/response/response-actions.spec.ts
  • tests/utils/page/actions.ts
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{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
Styled Components are used as wrappers to define both self and children components style; Tailwind classes are used specifically for layout based styles
Styled Component CSS might also change layout but Tailwind classes shouldn't define colors

Files:

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

Applied to files:

  • tests/response/response-actions.spec.ts
🧬 Code graph analysis (1)
packages/bruno-app/src/ui/ButtonDropdown/index.jsx (3)
packages/bruno-app/src/components/Icons/IconCaretDown/index.js (1)
  • IconCaretDown (3-16)
packages/bruno-app/src/ui/ButtonDropdown/StyledWrapper.js (1)
  • StyledWrapper (3-22)
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: Playwright E2E Tests
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: Unit Tests
  • GitHub Check: CLI Tests
  • GitHub Check: SSL Tests - Linux
🔇 Additional comments (6)
packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js (1)

286-297: Test id addition is clean and consistent

Adding data-testid="sidebar-collection-row" on the main collection row div is consistent with existing data-testid usage in this component and improves selector stability for tests without impacting runtime behavior. Looks good as-is.

tests/response/response-actions.spec.ts (1)

21-21: LGTM – URL change to internal test endpoint.

Switching to the internal testbench endpoint provides better control and reliability for E2E tests.

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

534-540: LGTM – clear and straightforward format switching.


546-553: LGTM – preview tab switching looks correct.


612-616: Export list properly updated.

All new helper functions are correctly exported.


486-494: Verify all call sites updated for new openRequest signature.

The function signature changed from openRequest(page, requestName) to openRequest(page, collectionName, requestName). Ensure all call sites throughout the test suite have been updated to pass the collection name as the second parameter.

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

Caution

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

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

151-159: Unused dependency responseEncoding in useMemo.

responseEncoding is included in the dependency array but isn't used within the memo's computation. Remove it to avoid unnecessary recalculations.

-    [data, dataBuffer, responseEncoding, selectedFormat, filter, isLargeResponse, showLargeResponse]
+    [data, dataBuffer, selectedFormat, filter, isLargeResponse, showLargeResponse]
♻️ Duplicate comments (1)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (1)

118-124: Third instance of buffer conversion logic.

This is the same try/catch Buffer.from pattern used in both hooks above. After extracting to a helper, use it here as well.

🧹 Nitpick comments (2)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (2)

13-31: Consider removing commented-out name properties.

The commented // name: 'Structured' and // name: 'Raw' lines appear to be unused. If these aren't planned for future use, remove them to keep the code clean.


49-55: Duplicate buffer conversion logic.

The Buffer.from conversion with try/catch is duplicated in both useInitialResponseFormat and useResponsePreviewFormatOptions. Extract this to a shared helper function:

const safeBufferFromBase64 = (dataBuffer) => {
  try {
    return dataBuffer ? Buffer.from(dataBuffer, 'base64') : null;
  } catch (error) {
    console.error('Error converting dataBuffer to Buffer:', error);
    return null;
  }
};

Also applies to: 73-79

📜 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 1bfacfd and 65e70e3.

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

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

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

Files:

  • packages/bruno-app/src/components/ResponsePane/QueryResult/index.js
🧬 Code graph analysis (1)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (4)
packages/bruno-app/src/utils/common/index.js (5)
  • dataBuffer (280-280)
  • dataBuffer (384-384)
  • contentType (93-93)
  • getContentType (83-119)
  • getContentType (83-119)
packages/bruno-app/src/utils/response/index.js (4)
  • detectContentTypeFromBuffer (178-260)
  • detectContentTypeFromBuffer (178-260)
  • getDefaultResponseFormat (8-59)
  • getDefaultResponseFormat (8-59)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (1)
  • QueryResultPreview (20-133)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultFilter/index.js (1)
  • QueryResultFilter (7-71)
⏰ 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: SSL Tests - Windows
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: Unit Tests
🔇 Additional comments (4)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (4)

1-11: LGTM!

Imports are well-organized and appropriate for the component's needs.


165-185: LGTM!

The previewMode memo correctly derives the preview type from selectedFormat and detectedContentType, with proper fallback handling for all format cases.


187-191: LGTM!

Clean approach using flatMap to search across option groups. Fallback to 'text/plain' is the correct standard MIME type.


223-237: LGTM!

Props are correctly wired to QueryResultPreview, and the data-testid attribute enables proper E2E testing.

Comment on lines +60 to +63
// Wait until both content types are available
if (detectedContentType === null || contentType === undefined) {
return { initialFormat: null, initialTab: null };
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Early return may be overly restrictive.

The hook returns { initialFormat: null, initialTab: null } when detectedContentType === null, even if contentType is available from headers. This could cause format selection to fail for valid text-based responses where buffer detection returns null but header-based content-type is present.

Consider proceeding with contentType alone when buffer detection fails:

-    if (detectedContentType === null || contentType === undefined) {
+    if (contentType === undefined) {
       return { initialFormat: null, initialTab: null };
     }
🤖 Prompt for AI Agents
In packages/bruno-app/src/components/ResponsePane/QueryResult/index.js around
lines 60-63, the hook currently returns early when detectedContentType === null
even if contentType (from headers) is present; update the condition so you only
early-return when both detection and header contentType are absent (e.g., when
detectedContentType is null/undefined AND contentType is null/undefined). This
lets the logic proceed using header-derived contentType when buffer detection
fails, while preserving the early return when no content type information exists
at all.

@abhishek-bruno abhishek-bruno force-pushed the feat/response-data-type-selector branch 2 times, most recently from 5664e1e to fd05725 Compare December 9, 2025 06:52
@abhishek-bruno abhishek-bruno force-pushed the feat/response-data-type-selector branch from 854826d to 1db2ef6 Compare December 9, 2025 16:21
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (11)
packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js (1)

24-43: Avoid nested interactive elements by removing the inner <button>

The default branch still renders a <button> inside a focusable div that acts as a button (tabIndex={0}, onClick, onKeyDown), which creates nested interactive controls and redundant tab stops. Flatten this by making the outer wrapper the sole interactive element and rendering only the icon inside.

Apply a minimal diff like this:

   return (
-    <div role={!!children ? 'button' : undefined} tabIndex={0} onClick={clearResponse} title={!children ? 'Clear response' : null} onKeyDown={handleKeyDown} data-testid="response-clear-button">
-      {children ? children : (
-        <StyledWrapper className="flex items-center">
-          <button className="p-1">
-            <IconEraser size={16} strokeWidth={2} />
-          </button>
-        </StyledWrapper>
-      )}
-    </div>
+    <div role={!!children ? 'button' : undefined} tabIndex={0} onClick={clearResponse} title={!children ? 'Clear response' : null} onKeyDown={handleKeyDown} data-testid="response-clear-button">
+      {children ? children : (
+        <StyledWrapper className="flex items-center p-1">
+          <IconEraser size={16} strokeWidth={2} />
+        </StyledWrapper>
+      )}
+    </div>
   );

This keeps the current API/behavior but removes the inner <button> so there is only one interactive element.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (1)

342-374: Fix case-sensitivity when detecting repeated child elements in xmlToObject

Repeated children are currently detected with:

if (
  childValue !== null ||
  elementChildren.filter((c) => c.nodeName.toLowerCase() === childName).length > 1
) {

Here childName preserves the original casing (e.g. 'Item'), so the comparison against c.nodeName.toLowerCase() fails for mixed‑case tags. For empty repeated elements, this can drop all instances from the parsed object.

Consider normalizing both sides and avoiding repeated filters:

-      if (elementChildren.length > 0) {
-        const childMap = {};
-        elementChildren.forEach((child) => {
-          const childName = child.nodeName; // Preserve original casing
-          const childValue = xmlToObject(child);
-
-          if (childValue !== null || elementChildren.filter((c) => c.nodeName.toLowerCase() === childName).length > 1) {
+      if (elementChildren.length > 0) {
+        const childMap = {};
+        const nameCounts = elementChildren.reduce((acc, c) => {
+          const key = c.nodeName.toLowerCase();
+          acc[key] = (acc[key] || 0) + 1;
+          return acc;
+        }, {});
+
+        elementChildren.forEach((child) => {
+          const childName = child.nodeName; // preserve original casing for keys
+          const childValue = xmlToObject(child);
+          const sameNameCount = nameCounts[childName.toLowerCase()] || 0;
+
+          if (childValue !== null || sameNameCount > 1) {
             if (childMap[childName]) {
               // Multiple children with same name - convert to array
               if (!Array.isArray(childMap[childName])) {
                 childMap[childName] = [childMap[childName]];
               }
               childMap[childName].push(childValue);
             } else {
               childMap[childName] = childValue;
             }
           }
         });

This keeps the exposed keys case‑preserving while making the repeated‑child detection case‑insensitive and avoids O(n²) filtering.

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

141-164: Fix nested interactive elements and always expose button semantics on the wrapper

The current markup:

<div
  role={!!children ? 'button' : undefined}
  tabIndex={isDisabled ? -1 : 0}
  aria-disabled={isDisabled}
  onKeyDown={handleKeyDown}
  onClick={handleSaveClick}
  // ...
>
  {children ?? (
    <StyledWrapper className="flex items-center">
      <button className="p-1">
        <IconBookmark size={16} strokeWidth={2} />
      </button>
    </StyledWrapper>
  )}
</div>

has two problems:

  1. Nested interactive elements – a focusable <div> wrapping a real <button> creates duplicate focus targets and confusing screen‑reader semantics.
  2. Inconsistent role – when children is not provided, the outer div is focusable (tabIndex=0) but has no role, so assistive tech won’t identify it as a button.

You can fix both by making the wrapper the only interactive element and rendering a non‑interactive icon inside:

-      <div
-        role={!!children ? 'button' : undefined}
+      <div
+        role="button"
         tabIndex={isDisabled ? -1 : 0}
         aria-disabled={isDisabled}
         onKeyDown={handleKeyDown}
         onClick={handleSaveClick}
@@
-        {children ?? (
-          <StyledWrapper className="flex items-center">
-            <button className="p-1">
-              <IconBookmark size={16} strokeWidth={2} />
-            </button>
-          </StyledWrapper>
-        )}
+        {children ?? (
+          <StyledWrapper className="flex items-center">
+            <span className="p-1" aria-hidden="true">
+              <IconBookmark size={16} strokeWidth={2} />
+            </span>
+          </StyledWrapper>
+        )}
       </div>

This keeps click/keyboard handling on the outer wrapper and avoids nested interactive controls.

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

155-171: Dead code: HTML parser never generates parsererror.

The parsererror check at lines 161-164 is ineffective because DOMParser with 'text/html' uses a lenient parser that never produces parsererror elements. This was flagged in a previous review. Consider removing the try-catch and parsererror check, or switch to 'application/xml' if strict validation is required.

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

53-67: Nested interactive elements accessibility issue persists.

The outer div (line 54) with role, tabIndex, onClick, and onKeyDown wraps the inner <button> (line 57) when no children are provided. This creates nested interactive elements - an accessibility anti-pattern that was flagged in a previous review.

When rendering the default UI (no children), the wrapper should not be interactive; only the inner <button> should handle interactions. When custom children are provided, the wrapper can be interactive.

Apply the fix from the previous review:

+ const isCustomRender = !!children;
+
  return (
-   <div role={!!children ? 'button' : undefined} tabIndex={0} onClick={handleClick} title={!children ? 'Copy response to clipboard' : null} onKeyDown={handleKeyDown} data-testid="response-copy-btn">
+   <div 
+     role={isCustomRender ? "button" : undefined}
+     tabIndex={isCustomRender ? 0 : undefined}
+     onClick={isCustomRender ? handleClick : undefined}
+     onKeyDown={isCustomRender ? handleKeyDown : undefined}
+     title={isCustomRender ? 'Copy response to clipboard' : undefined}
+     data-testid="response-copy-btn"
+   >
-     {children ? children : (
+     {isCustomRender ? children : (
        <StyledWrapper className="flex items-center">
-         <button className="p-1" disabled={!hasData}>
+         <button 
+           className="p-1" 
+           disabled={!hasData}
+           onClick={handleClick}
+           onKeyDown={handleKeyDown}
+           title="Copy response to clipboard"
+         >
            {copied ? (
              <IconCheck size={16} strokeWidth={2} />
            ) : (
              <IconCopy size={16} strokeWidth={2} />
            )}
          </button>
        </StyledWrapper>
      )}
    </div>
  );
packages/bruno-app/src/ui/ButtonDropdown/index.jsx (1)

2-2: Use custom IconCaretDown for codebase consistency.

The codebase has a custom IconCaretDown component at components/Icons/IconCaretDown/index.js. Using it ensures visual consistency.

-import { IconCaretDown } from '@tabler/icons';
+import IconCaretDown from 'components/Icons/IconCaretDown';

Note: You may need to adjust props since the custom icon has different defaults (e.g., size vs width/height).

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

46-68: Early return may be too restrictive.

Per past review: when detectedContentType === null, the hook returns nulls even if contentType from headers is available. Consider proceeding with header-based contentType alone when buffer detection fails.

-    if (detectedContentType === null || contentType === undefined) {
+    if (contentType === undefined) {
       return { initialFormat: null, initialTab: null };
     }
tests/utils/page/locators.ts (1)

82-82: Redundant body locator.

body (line 82) uses the identical selector .response-pane as pane (line 80). Either remove body or update its selector if targeting a different element.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (1)

87-88: Add alt attribute to image for accessibility.

The <img> element is missing an alt attribute, which is required for accessibility compliance.

-      return <img src={`data:${contentType.replace(/\;(.*)/, '')};base64,${dataBuffer}`} />;
+      return <img src={`data:${contentType.replace(/\;(.*)/, '')};base64,${dataBuffer}`} alt="Response preview" />;
tests/utils/page/actions.ts (2)

617-628: Copy-paste bug: switchToEditorTab is identical to switchToPreviewTab.

Both functions click the same preview-response-tab toggle. This is incorrect - switchToEditorTab should switch away from preview mode, not enable it.

 const switchToEditorTab = async (page: Page) => {
   await test.step('Switch to editor tab', async () => {
     const responseFormatTab = page.getByTestId('format-response-tab');
     await responseFormatTab.click();
-    const previewTab = page.getByTestId('preview-response-tab');
-    await previewTab.click();
+    const editorTab = page.getByTestId('editor-response-tab');
+    await editorTab.click();
   });
 };

Verify the correct test ID for the editor tab in the app implementation.


654-664: Add JSDoc and improve timing reliability.

Missing JSDoc per coding guidelines. Also, after clicking the menu, there's no wait for the dropdown to appear before clicking the action button.

+/**
+ * Click a response action button
+ * @param page - The page object
+ * @param actionTestId - The test ID of the action button to click
+ */
 const clickResponseAction = async (page: Page, actionTestId: string) => {
-  const actionButton = await page.getByTestId(actionTestId);
+  const actionButton = page.getByTestId(actionTestId);
   if (await actionButton.isVisible()) {
     await actionButton.click();
   } else {
-    const menu = await page.getByTestId('response-actions-menu');
+    const menu = page.getByTestId('response-actions-menu');
     await menu.click();
+    await actionButton.waitFor({ state: 'visible' });
     await actionButton.click();
   }
 };

Note: getByTestId returns a locator synchronously; no await needed.

🧹 Nitpick comments (7)
packages/bruno-app/src/utils/common/index.js (2)

273-423: New formatResponse branches cover the extra modes with sensible fallbacks

The added branches for html, javascript, hex, base64, and text/raw modes look logically sound:

  • Large responses short‑circuit to inexpensive stringification instead of heavy formatting.
  • HTML/JS modes use rawData and delegate to prettifyHtmlString / prettifyJavaScriptString with try/catch fallbacks.
  • Hex mode first returns existing hex‑like strings as‑is, otherwise formats a hex dump from the base64 buffer.
  • Base64 just returns dataBufferString, which is exactly what a “base64 view” would expect.
  • Text/raw returns rawData for normal-size responses and reasonable stringified fallbacks for very large ones.

One thing to keep an eye on is the top guard:

if (data === undefined || !dataBufferString || !mode) {
  return '';
}

If you ever introduce modes that intentionally work with an undefined data but a valid dataBufferString (e.g., strictly binary responses), this early return will bypass the hex/base64 branches. Today that may not happen, but it’s worth verifying the call sites once you start wiring more binary formats through.


466-558: HTML/JS prettifiers and hex helpers are mostly solid; two small points

  1. HTML detection is very strict
    isValidHtml currently checks only for an <html> tag:

    export const isValidHtml = (str) => {
      if (typeof str !== 'string' || !str.trim()) return false;
      return /<\s*html[\s>]/i.test(str);
    };

    That means HTML fragments like <div>ok</div> will be treated as “not HTML”. If callers intend to treat fragments as HTML (which is common for API responses), consider relaxing this to a broader structural check (e.g., presence of any tag) or document that “valid HTML” here specifically means a full document.

  2. Hex detection heuristics are conservative (which is good)
    isHexFormat’s combination of:

    • hex-dump pattern detection, and
    • plain-hex detection with min length, even length, and numeric‑digit requirement

    is a nice compromise to avoid misclassifying short words. No issues, just calling out that this is deliberately strict, which is appropriate for a “don’t auto-guess too aggressively” preview.

If you want, I can draft an isValidHtml variant that better matches the actual HtmlPreview use‑cases.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (1)

114-222: XmlNode rendering is generally solid; consider removing unused array bookkeeping

XmlNode already special‑cases arrays at the top:

if (Array.isArray(node)) {
  // ...
  return (<>{node.map(...</>);
}

Later you compute:

const hasArrayValue = Array.isArray(node);
const arrayLength = hasArrayValue ? node.length : 0;

but never actually use hasArrayValue or arrayLength. They can be safely removed to reduce noise:

-  // Determine if this node's value is an array
-  const hasArrayValue = Array.isArray(node);
-  const arrayLength = hasArrayValue ? node.length : 0;
-
   return (

Everything else in XmlNode/XmlArrayNode (expansion state, padding by depth, array rendering) looks good.

packages/bruno-app/src/ui/ButtonDropdown/index.jsx (1)

19-19: Remove redundant role="button" attribute.

Native <button> elements already have an implicit button role. The attribute is unnecessary.

       disabled={disabled}
       data-testid={props['data-testid']}
       style={style}
-      role="button"
       {...props}
packages/bruno-app/src/components/ResponsePane/index.js (1)

34-46: Duplicate state management pattern with QueryResponse.

This initialization pattern (hooks + state + effect) is duplicated in QueryResponse/index.js. Consider extracting a custom hook if both need to remain separate, or ensure only one component manages this state to avoid drift.

If intentional (different contexts), a shared custom hook would reduce duplication:

// useResponseFormatState.js
export const useResponseFormatState = (dataBuffer, headers) => {
  const { initialFormat, initialTab } = useInitialResponseFormat(dataBuffer, headers);
  const previewFormatOptions = useResponsePreviewFormatOptions(dataBuffer, headers);
  const [selectedFormat, setSelectedFormat] = useState('raw');
  const [selectedTab, setSelectedTab] = useState('editor');

  useEffect(() => {
    if (initialFormat !== null && initialTab !== null) {
      setSelectedFormat(initialFormat);
      setSelectedTab(initialTab);
    }
  }, [initialFormat, initialTab]);

  return { selectedFormat, setSelectedFormat, selectedTab, setSelectedTab, previewFormatOptions };
};
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (1)

1-1: Unused useMemo import.

useMemo is imported but never used in this component. Remove it to keep imports clean.

-import React, { useState, useMemo } from 'react';
+import React, { useState } from 'react';
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (1)

49-55: Duplicate buffer conversion logic.

Buffer conversion with identical try-catch appears in three places: useInitialResponseFormat (lines 49-55), useResponsePreviewFormatOptions (lines 73-79), and QueryResult (lines 118-124). Extract to a shared utility.

// In a utils file
const safeBufferFromBase64 = (dataBuffer) => {
  try {
    return dataBuffer ? Buffer.from(dataBuffer, 'base64') : null;
  } catch (error) {
    console.error('Error converting dataBuffer to Buffer:', error);
    return null;
  }
};

Also applies to: 73-79, 118-124

📜 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 4e94a50 and 854826d.

📒 Files selected for processing (66)
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/StyledWrapper.js (2 hunks)
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js (0 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/JsonPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/TextPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/VideoPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (4 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (4 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js (4 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.spec.js (4 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseSave/StyledWrapper.js (0 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js (0 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/StatusCode/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/StatusCode/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/index.js (5 hunks)
  • packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js (2 hunks)
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js (1 hunks)
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js (1 hunks)
  • packages/bruno-app/src/ui/ButtonDropdown/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/ui/ButtonDropdown/index.jsx (1 hunks)
  • packages/bruno-app/src/ui/ErrorAlert/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/ui/ErrorAlert/index.js (1 hunks)
  • packages/bruno-app/src/utils/common/codemirror.js (1 hunks)
  • packages/bruno-app/src/utils/common/index.js (4 hunks)
  • packages/bruno-app/src/utils/response/index.js (1 hunks)
  • tests/response-examples/create-example.spec.ts (6 hunks)
  • tests/response-examples/edit-example.spec.ts (6 hunks)
  • tests/response-examples/menu-operations.spec.ts (4 hunks)
  • tests/response/large-response-crash-prevention.spec.ts (1 hunks)
  • tests/response/response-actions.spec.ts (2 hunks)
  • tests/response/response-format-select-and-preview/fixtures/collection/bruno.json (1 hunks)
  • tests/response/response-format-select-and-preview/fixtures/collection/request-html.bru (1 hunks)
  • tests/response/response-format-select-and-preview/fixtures/collection/request-json.bru (1 hunks)
  • tests/response/response-format-select-and-preview/init-user-data/collection-security.json (1 hunks)
  • tests/response/response-format-select-and-preview/init-user-data/preferences.json (1 hunks)
  • tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts (1 hunks)
  • tests/utils/page/actions.ts (4 hunks)
  • tests/utils/page/locators.ts (3 hunks)
💤 Files with no reviewable changes (3)
  • packages/bruno-app/src/components/ResponsePane/ResponseSave/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js
✅ Files skipped from review due to trivial changes (3)
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/StyledWrapper.js
🚧 Files skipped from review as they are similar to previous changes (30)
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js
  • packages/bruno-app/src/utils/common/codemirror.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/StyledWrapper.js
  • tests/response/response-format-select-and-preview/fixtures/collection/request-json.bru
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js
  • tests/response/response-format-select-and-preview/fixtures/collection/request-html.bru
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/JsonPreview.js
  • packages/bruno-app/src/components/ResponsePane/StatusCode/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js
  • packages/bruno-app/src/ui/ErrorAlert/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/StatusCode/index.js
  • tests/response-examples/edit-example.spec.ts
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js
  • packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx
  • tests/response-examples/create-example.spec.ts
  • packages/bruno-app/src/ui/ErrorAlert/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/VideoPreview.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js
  • tests/response/large-response-crash-prevention.spec.ts
  • tests/response/response-format-select-and-preview/init-user-data/collection-security.json
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/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. 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/ResponsePane/ResponseLayoutToggle/index.spec.js
  • packages/bruno-app/src/ui/ButtonDropdown/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js
  • tests/response-examples/menu-operations.spec.ts
  • packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/index.js
  • packages/bruno-app/src/components/ResponsePane/index.js
  • tests/utils/page/actions.ts
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js
  • packages/bruno-app/src/utils/common/index.js
  • packages/bruno-app/src/utils/response/index.js
  • tests/response/response-actions.spec.ts
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/TextPreview.js
  • tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js
  • packages/bruno-app/src/ui/ButtonDropdown/index.jsx
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js
  • tests/utils/page/locators.ts
  • packages/bruno-app/src/components/ResponsePane/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/response/response-format-select-and-preview/fixtures/collection/bruno.json
  • tests/response-examples/menu-operations.spec.ts
  • tests/utils/page/actions.ts
  • tests/response/response-format-select-and-preview/init-user-data/preferences.json
  • tests/response/response-actions.spec.ts
  • tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts
  • tests/utils/page/locators.ts
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{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
Styled Components are used as wrappers to define both self and children components style; Tailwind classes are used specifically for layout based styles
Styled Component CSS might also change layout but Tailwind classes shouldn't define colors

Files:

  • packages/bruno-app/src/ui/ButtonDropdown/index.jsx
🧠 Learnings (7)
📚 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/ui/ButtonDropdown/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/StyledWrapper.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{jsx,tsx} : Use styled component's theme prop to manage CSS colors and not CSS variables when in the context of a styled component or any React component using the styled component

Applied to files:

  • packages/bruno-app/src/ui/ButtonDropdown/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/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/ui/ButtonDropdown/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/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 **/*.test.{js,jsx,ts,tsx} : Add tests for any new functionality or meaningful changes. If code is added, removed, or significantly modified, corresponding tests should be updated or created

Applied to files:

  • tests/response-examples/menu-operations.spec.ts
  • tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts
📚 Learning: 2025-12-02T09:45:31.709Z
Learnt from: sid-bruno
Repo: usebruno/bruno PR: 6266
File: packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js:38-38
Timestamp: 2025-12-02T09:45:31.709Z
Learning: In the ResponseCopy component (packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js), the copy button is intentionally disabled using `!response.data` to prevent copying stream resets which result in empty strings.

Applied to files:

  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js
  • packages/bruno-app/src/components/ResponsePane/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Add in JSDoc comments to add more details to the abstractions if needed

Applied to files:

  • packages/bruno-app/src/utils/common/index.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : No trailing commas

Applied to files:

  • tests/utils/page/locators.ts
🧬 Code graph analysis (12)
packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js (3)
packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js (1)
  • StyledWrapper (3-13)
packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js (1)
  • StyledWrapper (3-12)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js (1)
  • StyledWrapper (3-12)
tests/response-examples/menu-operations.spec.ts (1)
tests/utils/page/actions.ts (1)
  • clickResponseAction (692-692)
packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js (1)
packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js (1)
  • QueryResponse (8-58)
packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js (1)
packages/bruno-app/src/components/ResponsePane/ClearTimeline/StyledWrapper.js (1)
  • StyledWrapper (3-6)
packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/index.js (1)
packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js (1)
  • QueryResponse (8-58)
packages/bruno-app/src/components/ResponsePane/index.js (2)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (4)
  • useInitialResponseFormat (47-68)
  • useInitialResponseFormat (47-68)
  • useResponsePreviewFormatOptions (71-105)
  • useResponsePreviewFormatOptions (71-105)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx (1)
  • QueryResultTypeSelector (7-44)
packages/bruno-app/src/utils/common/index.js (1)
packages/bruno-js/src/bru.js (1)
  • xmlFormat (2-2)
packages/bruno-app/src/utils/response/index.js (2)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (1)
  • contentType (126-126)
packages/bruno-app/src/utils/common/index.js (3)
  • contentType (93-93)
  • i (21-21)
  • i (515-515)
tests/response/response-actions.spec.ts (1)
tests/utils/page/actions.ts (1)
  • clickResponseAction (692-692)
packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js (1)
packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js (1)
  • ResponseDownload (8-62)
tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts (2)
tests/utils/page/actions.ts (6)
  • closeAllCollections (667-667)
  • openRequest (684-684)
  • sendRequestAndWaitForResponse (688-688)
  • switchToPreviewTab (690-690)
  • switchToEditorTab (691-691)
  • switchResponseFormat (689-689)
tests/utils/page/locators.ts (1)
  • buildCommonLocators (3-102)
packages/bruno-app/src/ui/ButtonDropdown/index.jsx (3)
packages/bruno-app/src/components/Icons/IconCaretDown/index.js (1)
  • IconCaretDown (3-16)
packages/bruno-app/src/ui/ButtonDropdown/StyledWrapper.js (1)
  • StyledWrapper (3-22)
packages/bruno-app/src/components/Dropdown/index.js (1)
  • Dropdown (5-25)
🔇 Additional comments (51)
packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js (1)

18-18: LGTM! Intentional margin removal as part of UI refactor.

The removal of ml-2 aligns with the broader spacing adjustments across the response pane components in this PR. The component follows coding standards and maintains correct functionality.

packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/StyledWrapper.js (1)

171-171: LGTM – Whitespace changes align with styling cleanup.

The whitespace adjustments on these lines are harmless. Per the broader context, the removal of presentational styling from this wrapper aligns well with the shift toward modular preview components (HtmlPreview, JsonPreview, etc.), consistent with the learnings on separating Styled Components from layout concerns.

Optional: Consider visual regression testing to confirm that styling removals don't introduce unexpected UI changes in the Console RequestDetailsPanel.

Also applies to: 281-281

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

286-297: Test id addition is clear and non-invasive

data-testid="sidebar-collection-row" on the main row div is a good, descriptive hook for tests and does not alter runtime behavior. Looks good as-is.

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

37-41: Separator styling aligns well with theme usage

The new .separator rule is minimal, uses the theme for border color as expected, and fits cleanly into the existing StyledWrapper; no issues from a layout or theming perspective.

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

7-22: Hook extraction looks clean and idiomatic

useResponseClear neatly encapsulates the dispatch logic and keeps ResponseClear lean. No issues from a hooks/Redux usage perspective.

packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.spec.js (1)

83-107: Stable test locators for layout toggle look good

Switching to getByTestId('response-layout-toggle-button') keeps the tests robust against internal markup changes while still asserting the same UX (titles and state transitions). No issues from a Testing Library or behavior standpoint.

Also applies to: 109-172

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

1-14: StyledWrapper is consistent with other response action wrappers

Theme usage and hover styling are aligned with the existing ResponseCopy/ResponseClear/ResponseBookmark wrappers, so this is safe and consistent.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js (1)

1-13: Selector styling via theme tokens looks correct

Using .active and .preview-response-tab-label scoped within the StyledWrapper and driven by theme.colors.text.* is in line with the styling conventions and keeps colors centralized.

tests/response/response-format-select-and-preview/init-user-data/preferences.json (1)

1-6: Init user-data JSON is well-scoped to the new fixture

The preferences file correctly points lastOpenedCollections at the new fixtures/collection path and won’t affect non-test flows.

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

83-119: JavaScript content-type detection is reasonable

The new JAVASCRIPT_PATTERN (/^(application|text)\/(javascript|ecmascript)/i) and mapping to 'application/javascript' cover the common JS MIME types (application/javascript, text/javascript, application/ecmascript, text/ecmascript) and fit the existing normalization pattern used for JSON/XML/SVG. No issues here.


7-8: Prettier integration pattern is current best practice; bundle size consideration is appropriate

The prettier/standalone + parser-babel pattern with explicit plugin passing is the recommended approach for browser/Electron renderer environments. Standalone Prettier requires plugins to be passed explicitly in the format() call options, which is correctly handled in the code.

Note: For complete JavaScript/JSX support, ensure the estree plugin is also included alongside babel in the plugins array, as both are standard for the babel parser. The bundle size trade-off is reasonable given the existing 50MB threshold and conditional formatting logic.

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

27-40: Disabled and gating logic for streaming/large responses is aligned with copy behavior

The isResponseTooLarge / isStreamingResponse checks, combined into isDisabled, plus the early returns in handleSaveClick mirror the pattern used for copy actions (disabling on streaming and very large responses). This is a good guardrail to avoid saving unusable examples or accidentally triggering large operations.

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

6-30: Verify ResponseDownload prop handling and dropdown close behavior

The review raises a valid concern: if ResponseDownload doesn't accept and invoke the onClose prop, the dropdown menu won't close after downloading. Verify whether:

  1. ResponseDownload currently receives and handles the onClose prop
  2. The Dropdown component auto-closes on item click (confirming if manual close is needed)
  3. ResponseClear handles onClose for comparison

If these props are not wired through, implement the suggested change to ensure consistent UX where the menu closes after download completes.

packages/bruno-app/src/utils/response/index.js (5)

1-6: LGTM! Clean MIME type extraction.

The function safely handles edge cases with String() coercion and proper parameter stripping.


8-59: LGTM! Comprehensive format mapping.

The rules-based approach with sequential evaluation handles standard and custom MIME types (+json, +xml variants) effectively. The fallback ensures robustness.


61-70: LGTM! Standard HTML escaping.

Type guard and proper entity escaping for webview safety.


75-93: LGTM! Text detection with proper safeguards.

The empty buffer guard addresses the previous review concern. The 85% threshold and byte sampling provide reasonable text heuristics.


178-260: LGTM! Comprehensive content-type detection.

Excellent coverage of common binary formats via magic numbers with proper fallback chain. The MP3 detection bitwise operation correctly matches the sync word pattern.

packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js (1)

1-1: LGTM! Clean migration to QueryResponse.

The import and component usage updates align with the PR's modular response rendering architecture. The key prop provides stable React identity.

Also applies to: 17-25

tests/response/response-format-select-and-preview/fixtures/collection/bruno.json (1)

1-9: LGTM! Valid collection fixture.

The collection metadata follows the expected structure for test fixtures. The version "1" format is correct for this schema.

tests/response-examples/menu-operations.spec.ts (1)

4-4: LGTM! Consistent action helper usage.

Refactoring to use clickResponseAction improves test maintainability and provides consistent timeout/synchronization handling across bookmark actions.

Also applies to: 21-21, 52-52, 85-85

packages/bruno-app/src/ui/ButtonDropdown/StyledWrapper.js (1)

1-24: LGTM! Proper styled-components pattern.

Theme prop usage for colors aligns with project conventions. Clean component styling with proper hover states and separation concerns.

Based on learnings, styled-components with theme props for colors is the correct pattern here.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/TextPreview.js (1)

1-25: LGTM! Robust text preview with proper memoization.

The displayData transformation handles edge cases (null, objects, JSON.stringify failures) gracefully. Direct useMemo import and memo wrapper provide proper optimization.

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

40-45: LGTM! Proper keyboard handling with preventDefault.

The Space key preventDefault call addresses the previous review concern and prevents unwanted page scrolling.


6-35: LGTM! Clean hook extraction.

The useResponseCopy hook properly encapsulates copy logic, state management, and data validation. The 2-second copied state timeout and error handling are well implemented.

tests/response/response-actions.spec.ts (1)

3-3: LGTM! Consistent test helper adoption.

Migration to clickResponseAction aligns with the broader test refactoring pattern, improving consistency and maintainability across the test suite.

Also applies to: 30-30

packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/index.js (2)

10-10: LGTM!

Import correctly updated to use the new QueryResponse component which wraps QueryResult with format selection capabilities.


119-127: LGTM!

Props forwarded to QueryResponse match the expected interface. The disableRunEventListener={true} is appropriate for the Devtools console context.

tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts (3)

12-16: Good use of serial execution and cleanup.

Using test.describe.serial ensures tests run in order, and the afterAll cleanup properly closes collections.


29-33: Well-structured test steps with clear assertions.

Good use of test.step for readable test reports and multiple assertions per step.


22-27: Clean locator setup pattern.

Locator variables are properly defined and reused throughout the test, following best practices.

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

17-27: LGTM!

State initialization and synchronization with initial format/tab is cleanly handled. The null checks before setting state prevent unnecessary re-renders.


31-41: LGTM!

QueryResultTypeSelector is properly wired with format options and callbacks. The toggle logic for selectedTab is concise.


43-55: LGTM!

Props correctly forwarded to QueryResult. Conditional padding applied via classnames based on active tab.

packages/bruno-app/src/ui/ButtonDropdown/index.jsx (3)

70-73: LGTM!

Dropdown now properly closes after option selection. This was previously flagged and has been addressed.


7-28: LGTM!

ButtonIcon correctly forwards ref, passes disabled to the native button, and uses displayName for debugging. Clean implementation.


46-46: Guard against empty options array.

If options is an empty array, accessing options[0] returns undefined, and 'options' in undefined throws a TypeError.

-  const isGrouped = Array.isArray(options) && options.length > 0 && 'options' in options[0];
+  const isGrouped = Array.isArray(options) && options.length > 0 && options[0] && 'options' in options[0];

Likely an incorrect or invalid review comment.

packages/bruno-app/src/components/ResponsePane/index.js (4)

101-103: LGTM!

selectedFormat and selectedTab correctly passed to QueryResult for format-aware rendering.


201-216: LGTM!

QueryResultTypeSelector properly integrated with callbacks to update format and toggle preview mode. Conditional rendering for the 'response' tab is correct.


217-223: LGTM!

Status block cleanly displays response metadata with proper conditional rendering for streaming vs completed responses.


226-232: LGTM!

Actions correctly switch between ClearTimeline for timeline tab and ResponsePaneActions for response data. The null check on item?.response?.error prevents actions on errored responses.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (2)

64-80: LGTM!

Clean separation of the editor tab rendering path. The CodeEditor receives all necessary props.


109-119: LGTM!

New preview modes for JSON, text, and XML are well-structured and delegate to appropriate preview components.

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

572-589: LGTM!

Well-structured helper with proper JSDoc, configurable options, and sensible defaults.


591-602: LGTM!

Clean implementation with proper test.step wrapper for readable reports.


548-556: Breaking change: openRequest signature modified.

The function signature changed from (page, requestName) to (page, collectionName, requestName). Ensure all existing test callsites are updated to include the collectionName parameter when calling this function.

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

165-185: LGTM!

The previewMode memo has proper fallback handling and correct dependencies. Good fix from past review.


187-191: LGTM!

Fallback mode corrected to 'text/plain' - standard MIME type.


223-237: LGTM!

QueryResultPreview receives all necessary props with proper data flow.

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

83-88: LGTM!

New locators (editorContainer, formatTab, formatTabDropdown, previewContainer, codeLine, jsonTreeLine) are well-structured with appropriate test IDs. No trailing comma on the last property - compliant with coding guidelines.


122-122: LGTM!

Good migration to test-id-based locator for clearResponse.

Comment on lines +534 to +540
// const openRequest = async (page: Page, requestName: string) => {
// await test.step(`Open request "${requestName}"`, async () => {
// const locators = buildCommonLocators(page);
// await locators.sidebar.request(requestName).click();
// await expect(locators.tabs.activeRequestTab()).toContainText(requestName);
// });
// };
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove commented-out code.

Dead code should be removed rather than commented out. Version control preserves history if needed.

-// const openRequest = async (page: Page, requestName: string) => {
-//   await test.step(`Open request "${requestName}"`, async () => {
-//     const locators = buildCommonLocators(page);
-//     await locators.sidebar.request(requestName).click();
-//     await expect(locators.tabs.activeRequestTab()).toContainText(requestName);
-//   });
-// };
-
📝 Committable suggestion

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

Suggested change
// const openRequest = async (page: Page, requestName: string) => {
// await test.step(`Open request "${requestName}"`, async () => {
// const locators = buildCommonLocators(page);
// await locators.sidebar.request(requestName).click();
// await expect(locators.tabs.activeRequestTab()).toContainText(requestName);
// });
// };
🤖 Prompt for AI Agents
In tests/utils/page/actions.ts around lines 534–540 there is a commented-out
helper function block (openRequest) that is dead code; remove this entire
commented-out block from the file, run project lint/tests to ensure no
references break, and commit the change so version control retains history if
needed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (9)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js (1)

54-66: Nested interactive elements accessibility issue persists.

While role was made conditional, tabIndex={0}, onClick, and onKeyDown remain on the outer div regardless of whether children is provided. When children is falsy, this creates nested interactive elements (focusable outer div + focusable inner button), violating WCAG guidelines.

Solution: Make the outer wrapper interactive only when children exists:

  • When children is provided: outer div should have role="button", tabIndex={0}, and handlers
  • When children is falsy: outer div should be non-interactive (no tabIndex, no handlers on div), and the inner <button> should receive onClick/onKeyDown

Apply this refactor:

+ const isCustomRender = !!children;
+
  return (
-   <div role={!!children ? 'button' : undefined} tabIndex={0} onClick={handleClick} title={!children ? 'Copy response to clipboard' : null} onKeyDown={handleKeyDown} data-testid="response-copy-btn">
+   <div 
+     role={isCustomRender ? 'button' : undefined}
+     tabIndex={isCustomRender ? 0 : undefined}
+     onClick={isCustomRender ? handleClick : undefined}
+     onKeyDown={isCustomRender ? handleKeyDown : undefined}
+     title={isCustomRender ? 'Copy response to clipboard' : undefined}
+     data-testid="response-copy-btn"
+   >
-     {children ? children : (
+     {isCustomRender ? children : (
        <StyledWrapper className="flex items-center">
-         <button className="p-1" disabled={!hasData}>
+         <button 
+           className="p-1" 
+           disabled={!hasData}
+           onClick={handleClick}
+           onKeyDown={handleKeyDown}
+           title="Copy response to clipboard"
+         >
            {copied ? (
              <IconCheck size={16} strokeWidth={2} />
            ) : (
              <IconCopy size={16} strokeWidth={2} />
            )}
          </button>
        </StyledWrapper>
      )}
    </div>
  );
packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js (1)

85-104: Nested interactive elements still present.

The outer <div> with role="button" wraps an inner <button> at line 95, creating nested interactive elements. This was flagged in a previous review. Screen readers and keyboard navigation can behave unexpectedly with this structure.

       {children ? children : (
         <StyledWrapper className="flex items-center w-full">
-          <button className="p-1">
+          <span className="p-1">
             {orientation === 'horizontal' ? (
               <IconDockToBottom />
             ) : (
               <IconDockToRight />
             )}
-          </button>
+          </span>
         </StyledWrapper>
       )}
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (1)

61-62: Missing indentation in JSON.stringify.

This was flagged in a previous review. Objects won't be prettified without the third argument.

   } else if (typeof data === 'object') {
-    displayContent = JSON.stringify(data, null);
+    displayContent = JSON.stringify(data, null, 2);
   } else if (typeof data === 'string') {
packages/bruno-app/src/utils/response/index.js (1)

155-170: parsererror check is dead code for HTML parsing.

DOMParser with 'text/html' never produces a parsererror element - that's specific to XML parsing. The check is misleading.

   try {
     const parser = new DOMParser();
     const doc = parser.parseFromString(trimmed, 'text/html');
-
-    // Check for parsing errors
-    const parseError = doc.querySelector('parsererror');
-    if (parseError) {
-      return false;
-    }
-
-    // HTML parser is lenient; if we reach here with valid tags, consider it valid
     return true;
   } catch (error) {
     return false;
   }
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (1)

342-360: Case-sensitivity bug in repeated child detection.

The filter compares c.nodeName.toLowerCase() with childName (original casing). Tags like <Item> won't match because 'item' === 'Item' is false.

       elementChildren.forEach((child) => {
         const childName = child.nodeName; // Preserve original casing
         const childValue = xmlToObject(child);

-        if (childValue !== null || elementChildren.filter((c) => c.nodeName.toLowerCase() === childName).length > 1) {
+        if (childValue !== null || elementChildren.filter((c) => c.nodeName.toLowerCase() === childName.toLowerCase()).length > 1) {
           if (childMap[childName]) {
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (1)

87-89: Add alt attribute for accessibility.

Images should have an alt attribute for screen readers.

-      return <img src={`data:${contentType.replace(/\;(.*)/, '')};base64,${dataBuffer}`} />;
+      return <img src={`data:${contentType.replace(/\;(.*)/, '')};base64,${dataBuffer}`} alt="Response preview" />;
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (1)

60-63: Early return may discard valid header-based content type.

When detectedContentType === null (buffer detection fails) but contentType is available from headers, this returns null. Consider proceeding with header-based content type alone.

     // Wait until both content types are available
-    if (detectedContentType === null || contentType === undefined) {
+    if (contentType === undefined) {
       return { initialFormat: null, initialTab: null };
     }
packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js (1)

35-43: Flatten interactive structure and always expose a single button control.

The wrapper is focusable and clickable, but the default branch still renders a nested <button>, which recreates the “nested interactive elements” issue previously flagged. Also, the wrapper only gets role="button" when children are provided, so the default icon-only case is focusable but not semantically a button.

Keep a single interactive element (the wrapper) and remove the inner <button>, while always giving the wrapper role="button":

-  return (
-    <div role={!!children ? 'button' : undefined} tabIndex={0} onClick={clearResponse} title={!children ? 'Clear response' : null} onKeyDown={handleKeyDown} data-testid="response-clear-button">
-      {children ? children : (
-        <StyledWrapper className="flex items-center">
-          <button className="p-1">
-            <IconEraser size={16} strokeWidth={2} />
-          </button>
-        </StyledWrapper>
-      )}
-    </div>
-  );
+  return (
+    <div
+      role="button"
+      tabIndex={0}
+      onClick={clearResponse}
+      title={!children ? 'Clear response' : null}
+      onKeyDown={handleKeyDown}
+      data-testid="response-clear-button"
+    >
+      {children ? children : (
+        <StyledWrapper className="flex items-center p-1">
+          <IconEraser size={16} strokeWidth={2} />
+        </StyledWrapper>
+      )}
+    </div>
+  );

This removes nested controls, keeps a single tab stop, and ensures the default icon case is properly announced as a button.

tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts (1)

22-29: Remove page.pause() debugging call to keep tests non-interactive.

There’s a leftover await page.pause(); which will block automated runs and violates the “avoid page.pause in committed tests” guideline; the surrounding expect calls already provide deterministic synchronization, so this line should be deleted without replacement. As per coding guidelines, tests should rely on locators/expectations rather than interactive pauses.

🧹 Nitpick comments (28)
packages/bruno-app/src/ui/ErrorAlert/StyledWrapper.js (2)

3-14: Consider moving background colors into the theme instead of hard-coded RGBA + hex check

background-color is branching on props.theme.bg === '#1e1e1e' with literal RGBA values. That tightly couples this component to a specific hex and makes it harder to evolve themes or add new ones.

It would be more in line with your theming approach to have dedicated theme tokens for error/surface backgrounds (e.g. dark/light variants) and read them directly here instead of checking for a particular color value.

For example (adjust naming to your actual theme):

-  background-color: ${(props) => (props.theme.bg === '#1e1e1e' ? 'rgba(40, 40, 40, 0.5)' : 'rgba(250, 250, 250, 0.9)')};
+  background-color: ${(props) => props.theme.colors.background.error};

Then wire colors.background.error (or similar) to suitable dark/light values in the theme.

Based on learnings, using theme tokens for colors is preferred over inline color literals in styled components.


34-41: Revisit word-break: break-all for error messages (readability vs overflow)

word-break: break-all will break anywhere, including inside normal words, which can make stack traces or plain-text messages harder to read. Given you already use white-space: pre-wrap, you might get a better balance with something like overflow-wrap: anywhere or word-break: break-word (plus horizontal scrolling if needed), depending on how other panes handle long content.

Not a blocker, but worth considering if you see many long-but-readable messages truncated into hard-to-scan fragments.

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

13-26: Consider memoizing with useCallback.

The function works correctly but could benefit from memoization:

+import React, { useCallback } from 'react';
...
-  const saveResponseToFile = () => {
+  const saveResponseToFile = useCallback(() => {
     if (isDisabled) {
       return;
     }
     return new Promise((resolve, reject) => {
       ipcRenderer
         .invoke('renderer:save-response-to-file', response, item?.requestSent?.url, item.pathname)
         .then(resolve)
         .catch((err) => {
           toast.error(get(err, 'error.message') || 'Something went wrong!');
           reject(err);
         });
     });
-  };
+  }, [isDisabled, ipcRenderer, response, item?.requestSent?.url, item.pathname]);

28-39: Keyboard handling is correct.

Good accessibility implementation with Enter/Space support and proper disabled state handling. Optionally, consider memoizing with useCallback:

-  const handleKeyDown = (e) => {
+  const handleKeyDown = useCallback((e) => {
     if (isDisabled) {
       e.preventDefault();
       e.stopPropagation();
       return;
     }

     if (e.key === 'Enter' || e.key === ' ') {
       e.preventDefault();
       saveResponseToFile();
     }
-  };
+  }, [isDisabled, saveResponseToFile]);

48-48: Use undefined consistently for conditional attributes.

For consistency with the role attribute pattern (which should use undefined), use undefined instead of null for the conditional title.

-      title={!children ? 'Save response to file' : null}
+      title={!children ? 'Save response to file' : undefined}
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/TextPreview.js (1)

8-13: Add indentation to JSON.stringify for readability.

Objects will render as a single line without the third argument.

       try {
-        return JSON.stringify(data);
+        return JSON.stringify(data, null, 2);
       } catch {
         return String(data);
       }
packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js (1)

17-27: Consider extracting shared format initialization logic.

This initialization pattern (hook calls + useEffect sync) is duplicated in ResponsePane/index.js. A custom hook like useFormatState(dataBuffer, headers) could encapsulate both the initial format derivation and the state management.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (3)

28-33: Move isValidTreeData outside the component.

This pure function is recreated on every render. Move it to module scope to avoid unnecessary allocations.

+const isValidTreeData = (data) => {
+  if (data === null || data === undefined) return false;
+  if (typeof data === 'object' && !Array.isArray(data)) return true;
+  if (Array.isArray(data)) return true;
+  return false;
+};
+
 export default function XmlPreview({ data, defaultExpanded = true }) {
   // ...
-  const isValidTreeData = (data) => {
-    if (data === null || data === undefined) return false;
-    if (typeof data === 'object' && !Array.isArray(data)) return true;
-    if (Array.isArray(data)) return true;
-    return false;
-  };

219-221: Remove unused variable arrayLength.

arrayLength is computed but never used. This is dead code.

   // Determine if this node's value is an array
   const hasArrayValue = Array.isArray(node);
-  const arrayLength = hasArrayValue ? node.length : 0;

130-132: Consider more stable keys for array items.

Using index alone as key (key={idx}) can cause issues if items are reordered. Consider prefixing with nodeName for better stability:

-            key={idx}
+            key={`${displayNodeName}-${idx}`}
packages/bruno-app/src/utils/response/index.js (1)

254-257: Use subarray instead of slice for consistency.

buffer.slice() creates a copy while subarray() creates a view. Since you already use subarray on line 184, use it here too for consistency and slight memory efficiency.

   // Check if it's likely text (UTF-8)
-  if (isLikelyText(buffer.slice(0, Math.min(512, buffer.length)))) {
+  if (isLikelyText(buffer.subarray(0, Math.min(512, buffer.length)))) {
     return 'text/plain';
   }
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (1)

1-1: Remove unused useMemo import.

useMemo is imported but not used in this file.

-import React, { useState, useMemo } from 'react';
+import React, { useState } from 'react';
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (3)

46-79: Consider extracting duplicate buffer creation logic.

The buffer creation with try-catch is duplicated in both useInitialResponseFormat and useResponsePreviewFormatOptions. Extract to a shared helper for DRY.

+const createBufferFromBase64 = (dataBuffer) => {
+  try {
+    return dataBuffer ? Buffer.from(dataBuffer, 'base64') : null;
+  } catch (error) {
+    console.error('Error converting dataBuffer to Buffer:', error);
+    return null;
+  }
+};
+
 export const useInitialResponseFormat = (dataBuffer, headers) => {
   return useMemo(() => {
-    let buffer = null;
-    try {
-      buffer = dataBuffer ? Buffer.from(dataBuffer, 'base64') : null;
-    } catch (error) {
-      console.error('Error converting dataBuffer to Buffer:', error);
-      buffer = null;
-    }
+    const buffer = createBufferFromBase64(dataBuffer);
     // ... rest

118-125: Buffer creation duplicated third time.

This is the third instance of the same buffer creation pattern. Consolidate with the helper suggested earlier.

-  let buffer = null;
-  try {
-    buffer = Buffer.from(dataBuffer, 'base64');
-  } catch (error) {
-    console.error('Error converting dataBuffer to Buffer:', error);
-    buffer = null;
-  }
+  const buffer = createBufferFromBase64(dataBuffer);

151-159: Remove stale responseEncoding dependency.

responseEncoding is in the dependency array but not used in the computation.

-    [data, dataBuffer, responseEncoding, selectedFormat, filter, isLargeResponse, showLargeResponse]
+    [data, dataBuffer, selectedFormat, filter, isLargeResponse, showLargeResponse]
packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/StyledWrapper.js (1)

1-7: Empty StyledWrapper and potential dead code

This StyledWrapper doesn’t define any styles and isn’t used by ResponsePaneActions/index.js (that file imports ../StyledWrapper, not ./StyledWrapper). Right now it’s effectively a no-op wrapper plus an extra module.

Either:

  • Wire this up from ResponsePaneActions/index.js (likely ./StyledWrapper) and add any intended theme-based styles, or
  • Drop this file and use a plain div until you actually need a styled wrapper.
packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js (4)

15-41: Add type="button" and consider basic accessibility props on StyledMenuIcon

StyledMenuIcon is a plain button with no type, so it defaults to type="submit" and could accidentally submit a surrounding <form> if this component is ever rendered inside one.

Consider:

-const StyledMenuIcon = styled.button`
+const StyledMenuIcon = styled.button.attrs({ type: 'button' })`

You may also want to add aria-label="More actions" to MenuIcon for screen readers (the title helps but aria-label is more explicit).


43-80: Space detection logic is DOM-structure dependent; confirm container assumptions

checkSpace relies on actionsRef.current?.parentElement as the “actions container” and walks previousElementSibling to sum sibling widths. This assumes:

  • The parent of StyledWrapper is the horizontal container whose siblings represent the other right-side elements, and
  • All content to consider is to the left of that parent (no following siblings are relevant).

If the DOM around this component changes (e.g., another wrapper/div is inserted or the actions node moves), this logic could silently break and flip between inline/buttons and menu incorrectly.

At minimum, I’d suggest:

  • Adding a short comment documenting the expected structure (.right-side-container > [siblings...] + actions-container), and
  • Verifying in the surrounding JSX that actionsRef is attached to an element with the expected parent/ancestors.

82-105: ResizeObserver + debounce lifecycle looks good, but watch for stale refs

The ResizeObserver setup with a debounced checkSpace and cleanup via disconnect() and debouncedCheckSpace.cancel() is solid and should avoid leaks or post-unmount calls.

Given checkSpace closes over actionsRef and the ref value can change if this component ever re-renders into a different DOM position, just ensure actionsRef always refers to the same DOM node across the component’s lifetime (or be ready to re-run the effect if that assumption changes). Right now this is fine but a brief code comment about that assumption would help future maintainers.


120-185: Action rendering is clear; consider minor consistency and ResponseCopy behavior checks

The inline vs dropdown rendering is straightforward and the dropdown items correctly close the menu via onClick={closeDropdown}.

A few small points:

  • The dropdown “Download response” item is plain text, while the others wrap the label in a <span>; for consistency/selector reuse, you might want to wrap that label in a <span> too.
  • The same action set is duplicated between dropdown and inline modes. If this grows further, consider extracting a small config list (icon, label, component, props) to reduce divergence risk.
  • For ResponseCopy, please double-check that the dropdown version still respects the !response.data disabling behavior used in the toolbar button, so we don’t allow a menu click that attempts to copy an empty/stream-reset response. Based on learnings, this is intentionally guarded in ResponseCopy.
tests/response-examples/create-example.spec.ts (1)

4-4: Good reuse of clickResponseAction; update the stale timeout comment.

Routing all response-bookmark-btn clicks through clickResponseAction improves resilience and keeps the response-actions logic centralized. The inline comment on Line 20 still talks about “wait for 30 seconds” tied to the old explicit-timeout click; consider rephrasing it to describe the helper’s behavior generically (or just mention “wait for the bookmark button to appear”) so the comment doesn’t drift from what the helper actually does.

Also applies to: 20-22, 41-44, 65-68, 79-82, 87-88, 102-105

packages/bruno-app/src/utils/common/index.js (7)

105-107: JS content-type detection and normalization look good; legacy types optional.

The JAVASCRIPT_PATTERN correctly normalizes common JS MIME types to application/javascript, consistent with how JSON/XML are handled. If you care about older servers, you could optionally extend the regex to accept application/x-javascript / text/x-javascript, but it’s not strictly necessary.

Also applies to: 114-115


325-347: HTML formatting branch is consistent; consider a defensive fallback when rawData is empty.

The HTML path mirrors the large-response handling used elsewhere, which keeps behavior consistent. As a small robustness tweak, you might fall back to data (string or stringified) when rawData is empty (e.g., if base64 decoding ever fails) so you don’t end up with a blank preview despite having some usable data.


349-372: JavaScript formatting logic is sound; large-response fallback could be shared.

The JavaScript branch mirrors the HTML behavior and safely falls back to the original string on formatter failure. The large-response fallback logic is repeated across HTML, JavaScript, and text/raw; extracting a small helper (e.g., formatLargeResponseFallback(data)) would reduce duplication and keep behavior aligned.


374-423: Hex view may be expensive for very large responses; consider reusing buffer and enforcing a size cap.

The hex branch always re-decodes dataBufferString and generates a full hex dump regardless of bufferThreshold, which can allocate a large Buffer plus a very large string for big payloads. To avoid UI stalls or OOM on huge responses, consider (a) reusing the buffer already decoded at the top of formatResponse instead of decoding again, and (b) short-circuiting or truncating hex output when bufferSize exceeds a safe limit (similar to how other modes respect isVeryLargeResponse).


485-503: JavaScript prettifier behavior is reasonable; optional logging on failure.

Falling back to the original string on formatting errors avoids breaking the UI. If you ever need to debug malformed input, you might optionally log or surface the error (as you do in prettifyHtmlString), but that’s not required.


530-558: Hex detection heuristic looks robust; note short-hex false negatives.

The combination of hex-dump pattern, minimum length, even length, and requiring at least one digit should avoid prior false positives like dead/beef while still catching realistic hex data. This will intentionally treat short all-letter hex strings (e.g., ff, abcd) as non-hex, which seems like a reasonable trade-off.


7-8: Bundle size impact from Prettier runtime import.

Importing prettier/standalone with the babel parser will bundle the entire Prettier runtime into the application. For Electron-only usage this is acceptable, but if this utility is reused in web builds, consider lazy-loading these imports or scoping them only to contexts that require JS formatting to minimize bundle size.

Note: The prettier/parser-babel import path is compatible across Prettier v2 and v3, so no version-specific adjustment is needed.

📜 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 854826d and 1db2ef6.

📒 Files selected for processing (66)
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/StyledWrapper.js (2 hunks)
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js (0 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/JsonPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/TextPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/VideoPreview.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (4 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (4 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js (4 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.spec.js (4 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseSave/StyledWrapper.js (0 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js (0 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/StatusCode/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/StatusCode/index.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js (2 hunks)
  • packages/bruno-app/src/components/ResponsePane/index.js (5 hunks)
  • packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js (2 hunks)
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js (1 hunks)
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js (1 hunks)
  • packages/bruno-app/src/ui/ButtonDropdown/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/ui/ButtonDropdown/index.jsx (1 hunks)
  • packages/bruno-app/src/ui/ErrorAlert/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/ui/ErrorAlert/index.js (1 hunks)
  • packages/bruno-app/src/utils/common/codemirror.js (1 hunks)
  • packages/bruno-app/src/utils/common/index.js (4 hunks)
  • packages/bruno-app/src/utils/response/index.js (1 hunks)
  • tests/response-examples/create-example.spec.ts (6 hunks)
  • tests/response-examples/edit-example.spec.ts (6 hunks)
  • tests/response-examples/menu-operations.spec.ts (4 hunks)
  • tests/response/large-response-crash-prevention.spec.ts (1 hunks)
  • tests/response/response-actions.spec.ts (2 hunks)
  • tests/response/response-format-select-and-preview/fixtures/collection/bruno.json (1 hunks)
  • tests/response/response-format-select-and-preview/fixtures/collection/request-html.bru (1 hunks)
  • tests/response/response-format-select-and-preview/fixtures/collection/request-json.bru (1 hunks)
  • tests/response/response-format-select-and-preview/init-user-data/collection-security.json (1 hunks)
  • tests/response/response-format-select-and-preview/init-user-data/preferences.json (1 hunks)
  • tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts (1 hunks)
  • tests/utils/page/actions.ts (4 hunks)
  • tests/utils/page/locators.ts (3 hunks)
💤 Files with no reviewable changes (3)
  • packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseSave/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js
✅ Files skipped from review due to trivial changes (2)
  • tests/response/response-format-select-and-preview/fixtures/collection/request-html.bru
  • packages/bruno-app/src/components/ResponsePane/StatusCode/StyledWrapper.js
🚧 Files skipped from review as they are similar to previous changes (34)
  • packages/bruno-app/src/ui/ButtonDropdown/StyledWrapper.js
  • packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/StyledWrapper.js
  • packages/bruno-app/src/ui/ButtonDropdown/index.jsx
  • packages/bruno-app/src/utils/common/codemirror.js
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/StyledWrapper.js
  • tests/response/response-format-select-and-preview/init-user-data/preferences.json
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js
  • tests/utils/page/actions.ts
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/VideoPreview.js
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/StyledWrapper.js
  • tests/response-examples/edit-example.spec.ts
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/JsonPreview.js
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseBookmark/index.js
  • packages/bruno-app/src/ui/ErrorAlert/index.js
  • packages/bruno-app/src/components/Devtools/Console/RequestDetailsPanel/StyledWrapper.js
  • tests/response/large-response-crash-prevention.spec.ts
  • packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js
  • tests/response/response-format-select-and-preview/fixtures/collection/request-json.bru
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseStopWatch/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.spec.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/StyledWrapper.js
  • tests/response/response-format-select-and-preview/init-user-data/collection-security.json
  • packages/bruno-app/src/components/ResponsePane/StatusCode/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/index.jsx
  • packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js
  • tests/response/response-format-select-and-preview/fixtures/collection/bruno.json
🧰 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/Sidebar/Collections/Collection/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/index.js
  • packages/bruno-app/src/components/ResponsePane/QueryResponse/index.js
  • packages/bruno-app/src/utils/common/index.js
  • packages/bruno-app/src/utils/response/index.js
  • tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts
  • tests/response-examples/create-example.spec.ts
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js
  • packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js
  • packages/bruno-app/src/ui/ErrorAlert/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/TextPreview.js
  • packages/bruno-app/src/components/ResponsePane/ResponseTime/index.js
  • tests/response/response-actions.spec.ts
  • packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js
  • tests/utils/page/locators.ts
  • tests/response-examples/menu-operations.spec.ts
  • packages/bruno-app/src/components/ResponsePane/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js
  • packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.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/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts
  • tests/response-examples/create-example.spec.ts
  • tests/response/response-actions.spec.ts
  • tests/utils/page/locators.ts
  • tests/response-examples/menu-operations.spec.ts
🧠 Learnings (8)
📚 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/ResponsePane/ResponseCopy/StyledWrapper.js
  • packages/bruno-app/src/ui/ErrorAlert/StyledWrapper.js
📚 Learning: 2025-12-02T09:45:31.709Z
Learnt from: sid-bruno
Repo: usebruno/bruno PR: 6266
File: packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js:38-38
Timestamp: 2025-12-02T09:45:31.709Z
Learning: In the ResponseCopy component (packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js), the copy button is intentionally disabled using `!response.data` to prevent copying stream resets which result in empty strings.

Applied to files:

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

Applied to files:

  • packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js
  • packages/bruno-app/src/ui/ErrorAlert/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/ResponsePane/ResponseCopy/StyledWrapper.js
  • packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/StyledWrapper.js
  • packages/bruno-app/src/ui/ErrorAlert/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 **/*.{js,jsx,ts,tsx} : Add in JSDoc comments to add more details to the abstractions if needed

Applied to files:

  • packages/bruno-app/src/utils/common/index.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Applied to files:

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

Applied to files:

  • tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts
  • tests/response-examples/menu-operations.spec.ts
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : No trailing commas

Applied to files:

  • tests/utils/page/locators.ts
🧬 Code graph analysis (12)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (2)
packages/bruno-app/src/utils/common/index.js (2)
  • isValidHtml (506-509)
  • isValidHtml (506-509)
packages/bruno-app/src/utils/response/index.js (2)
  • isValidHtmlSnippet (98-171)
  • isValidHtmlSnippet (98-171)
packages/bruno-app/src/utils/response/index.js (3)
packages/bruno-app/src/components/ResponsePane/QueryResult/index.js (1)
  • contentType (126-126)
packages/bruno-app/src/utils/common/index.js (4)
  • contentType (93-93)
  • i (21-21)
  • i (515-515)
  • trimmed (537-537)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (1)
  • parser (302-302)
tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts (3)
tests/utils/page/actions.ts (6)
  • closeAllCollections (694-694)
  • openRequest (711-711)
  • sendRequestAndWaitForResponse (716-716)
  • switchToPreviewTab (718-718)
  • switchToEditorTab (719-719)
  • switchResponseFormat (717-717)
tests/utils/page/locators.ts (1)
  • buildCommonLocators (3-102)
packages/bruno-app/src/utils/codemirror/brunoVarInfo.js (1)
  • editorContainer (298-298)
tests/response-examples/create-example.spec.ts (1)
tests/utils/page/actions.ts (1)
  • clickResponseAction (720-720)
packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/StyledWrapper.js (7)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultTypeSelector/StyledWrapper.js (1)
  • StyledWrapper (3-11)
packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js (1)
  • StyledWrapper (3-58)
packages/bruno-app/src/components/ResponsePane/ResponseBookmark/StyledWrapper.js (1)
  • StyledWrapper (3-13)
packages/bruno-app/src/components/ResponsePane/ResponseClear/StyledWrapper.js (1)
  • StyledWrapper (3-12)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js (1)
  • StyledWrapper (3-12)
packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js (1)
  • StyledWrapper (3-12)
packages/bruno-app/src/components/ResponsePane/StyledWrapper.js (1)
  • StyledWrapper (3-42)
packages/bruno-app/src/components/ResponsePane/ClearTimeline/index.js (1)
packages/bruno-app/src/components/ResponsePane/ClearTimeline/StyledWrapper.js (1)
  • StyledWrapper (3-6)
packages/bruno-app/src/components/ResponsePane/ResponseTime/index.js (1)
packages/bruno-app/src/components/ResponsePane/StyledWrapper.js (1)
  • StyledWrapper (3-42)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (5)
packages/bruno-app/src/utils/common/index.js (3)
  • parsed (318-318)
  • i (21-21)
  • i (515-515)
packages/bruno-app/src/ui/ErrorAlert/index.js (1)
  • ErrorAlert (5-23)
packages/bruno-app/src/ui/ErrorAlert/StyledWrapper.js (1)
  • StyledWrapper (3-42)
packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/StyledWrapper.js (1)
  • StyledWrapper (3-75)
packages/bruno-app/src/utils/response/index.js (2)
  • parser (157-157)
  • i (80-80)
packages/bruno-app/src/components/ResponsePane/ResponseLayoutToggle/index.js (3)
packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js (1)
  • useResponseLayoutToggle (44-44)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js (1)
  • handleKeyDown (40-45)
packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js (1)
  • handleKeyDown (27-32)
packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js (2)
packages/bruno-app/src/components/ResponsePane/index.js (1)
  • response (67-67)
packages/bruno-app/src/components/ResponsePane/ResponseDownload/StyledWrapper.js (1)
  • StyledWrapper (3-12)
tests/response-examples/menu-operations.spec.ts (1)
tests/utils/page/actions.ts (1)
  • clickResponseAction (720-720)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js (2)
packages/bruno-app/src/components/ResponsePane/index.js (1)
  • response (67-67)
packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js (1)
  • StyledWrapper (3-12)
⏰ 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). (2)
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: Playwright E2E Tests
🔇 Additional comments (41)
packages/bruno-app/src/components/ResponsePane/ResponseSize/index.js (1)

21-24: Spacing tweak in wrapper looks good

The switch to ml-2 is consistent with the tighter ResponsePane spacing described in the PR and does not affect behavior or logic. No further changes needed here.

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

286-297: Stable test selector for collection row looks good

Adding data-testid="sidebar-collection-row" on the main row container is a clean way to target collections in tests without coupling to layout classes or IDs. No behavioral impact, and consistent with the sidebar naming scheme.

packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js (1)

497-508: Item row test id is appropriate and consistent

data-testid="sidebar-collection-item-row" on the main item row wrapper provides a stable, semantic selector aligned with the collection row test id. Works well for E2E and unit tests to distinguish item rows from collection rows.

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

17-22: Layout-only tweak looks good

Removing the ml-2 and keeping flex items-center simplifies spacing on this wrapper without affecting behavior. As long as this still visually aligns with neighboring controls in the ResponsePane, this change is good to go.

Please double-check the rendered ResponsePane to confirm that Clear Timeline still lines up correctly with the new response data type selector and other actions.

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

20-20: LGTM! Spacing adjustment aligns with layout changes.

The margin reduction from ml-4 to ml-2 is consistent with the broader UI layout refinements in this PR and maintains proper visual hierarchy in the response pane.

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

5-11: LGTM! Theme-based styling applied correctly.

The updated color tokens and hover states follow the established pattern of using theme props for colors, consistent with the project's styling approach.

Based on learnings, styled components correctly use theme props for color management.

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

6-35: Well-structured hook extraction.

The useResponseCopy hook properly encapsulates the copy logic and state management, making it reusable across the application.


40-45: Keyboard handler correctly prevents default behavior.

The Space key now includes preventDefault(), resolving the previous scroll issue.

packages/bruno-app/src/ui/ErrorAlert/StyledWrapper.js (2)

15-26: Close button styling looks consistent and minimal

Using opacity + a short transition for hover, and pulling the SVG color from props.theme.text, fits well with the rest of your theming and keeps the control visually lightweight.


28-32: Title styling matches intent and theme usage

The .error-title using a medium weight, small bottom margin, and theme.colors.text.danger aligns well with an error-heading role and keeps the color consistent with the rest of the design system.

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

1-6: LGTM!

Import statements are clean and all dependencies are utilized in the component.


8-11: LGTM!

Component initialization is correct. The !response.dataBuffer disabled check aligns with the pattern used in ResponseCopy and ensures the download action only works when buffer data is available.


53-59: Critical: Nested button elements violate accessibility guidelines.

Line 55 creates a <button> element inside a parent div that already has button semantics (role, onClick, keyboard handlers). This nested interactive element pattern creates accessibility issues for screen readers and keyboard navigation.

Apply this diff to replace the inner button with a non-interactive element:

       {children ? children : (
         <StyledWrapper className="flex items-center">
-          <button className="p-1">
+          <div className="p-1">
             <IconDownload size={16} strokeWidth={2} />
-          </button>
+          </div>
         </StyledWrapper>
       )}

Likely an incorrect or invalid review comment.

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

51-70: LGTM! Clean hook extraction.

The useResponseLayoutToggle hook cleanly encapsulates orientation state and toggle logic, making it reusable across components like ResponsePaneActions.

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

41-46: Format resets on response change — intentional?

This effect resets selectedFormat and selectedTab whenever initialFormat/initialTab change (i.e., when response content changes). If a user manually selects a format then re-runs the request, their selection will be overwritten.

If this is intentional, consider adding a brief comment. If not, you may want to track whether the user has manually changed the format.


101-102: LGTM! Props correctly passed to QueryResult.

The selectedFormat and selectedTab props are properly wired to the child component, enabling format-driven rendering.


203-213: LGTM! Format selector integration is clean.

The QueryResultTypeSelector is properly wired with format options, current value, and handlers. The toggle logic for selectedTab is correct.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/TextPreview.js (1)

18-22: LGTM! Styling is appropriate for text preview.

Monospace font, whitespace preservation, and overflow handling are well-configured.

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

28-56: LGTM! Component structure is clean.

The header/content separation and prop passing to QueryResult follow the established patterns from ResponsePane.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/HtmlPreview.js (2)

9-32: MutationObserver for drag detection is effective but has overhead.

This observer runs on every class attribute change in the subtree. If many HtmlPreview components are mounted simultaneously, this could add up. Consider using a React context or event-based approach if performance becomes an issue.


34-54: LGTM! HTML rendering with base URL injection is well-handled.

The escapeHtml usage prevents XSS in the base URL, and the webview configuration disables dialogs appropriately. The drag-blocking styles ensure smooth UX during pane resizing.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/XmlPreview/index.js (2)

74-112: LGTM!

Clean implementation with proper React state management for expand/collapse. Good use of aria-expanded for accessibility.


379-396: LGTM!

Helper functions are simple and correctly implemented.

packages/bruno-app/src/utils/response/index.js (4)

1-6: LGTM!

Clean MIME type extraction with proper fallback handling.


8-59: LGTM!

Well-structured rule-based format detection with comprehensive coverage.


61-70: LGTM!

Standard HTML escaping implementation for XSS prevention.


75-93: LGTM!

Good heuristic for text detection with proper guard against empty buffers.

packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js (2)

64-80: LGTM!

Clean conditional rendering for editor mode with proper prop passing.


109-119: LGTM!

Clean implementation of the new preview modes for JSON, text, and XML content.

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

13-31: Verify JSON CodeMirror mode.

'application/ld+json' is specific to JSON-LD. For generic JSON responses, 'application/json' may be more appropriate. Both should work, but confirm this is intentional.


165-185: LGTM!

Clean preview mode derivation with proper fallback handling and correct dependencies.


223-237: LGTM!

QueryResultPreview integration with new props is clean and well-structured.

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

7-22: Hook extraction looks good.

useResponseClear cleanly encapsulates the dispatch logic and keeps ResponseClear focused on UI concerns. No issues from a React/Redux perspective.


24-32: Keyboard handling for Enter/Space is now correct.

Using onKeyDown with Enter/Space plus e.preventDefault() avoids page scroll and makes the control keyboard-activatable, which is appropriate given the role="button" usage on the wrapper.

tests/response-examples/menu-operations.spec.ts (1)

4-4: Centralized bookmark clicks via clickResponseAction look solid.

Using clickResponseAction(page, 'response-bookmark-btn') for all three example-creation flows keeps the tests DRY and delegates timing/visibility concerns to the shared helper without changing test intent.

Also applies to: 20-22, 50-53, 83-86

tests/response/response-format-select-and-preview/response-format-select-and-preview.spec.ts (1)

12-88: Comprehensive coverage of response format selection and preview.

This suite does a nice job exercising the new format selector end-to-end: JSON and HTML responses, editor vs preview tabs, and all major formats (JSON, HTML, XML, JavaScript, Raw, Hex, Base64) with both editor contents and preview behavior asserted. The use of common locators and action helpers keeps the tests readable and aligned with our E2E guidelines for new functionality. Based on learnings, this gives the new feature solid automated coverage.

Also applies to: 90-160

tests/response/response-actions.spec.ts (1)

3-3: Consistent handling of response copy via clickResponseAction.

Swapping the explicit response-copy-btn click for clickResponseAction(page, 'response-copy-btn') keeps response actions consistent with the rest of the suite while the toast assertion still verifies the user-visible behavior.

Also applies to: 29-31

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

60-60: New response/environment/import locators look consistent with the new flows.

The added response locators (copy button, editor/preview containers, format tab/dropdown, code/json tree lines), the envOption helpers, and the clearResponse test-id locator all line up with how the new tests exercise the response pane and import flows, and they respect the no-trailing-comma style rule. As per coding guidelines, this keeps selectors centralized and maintainable for the expanded response UI.

Also applies to: 81-88, 99-100, 122-122

packages/bruno-app/src/utils/common/index.js (3)

466-483: HTML prettifier is straightforward and safely falls back.

Using xmlFormat with sensible options and catching errors to return the original string gives a nice best-effort formatter without risking broken output. This helper looks good as-is.


505-509: HTML validity check is intentionally strict; confirm callers expect full documents.

isValidHtml only returns true when an <html> root tag is present, so HTML fragments (<div>...) will be treated as invalid. That’s fine if you only consider full documents as “HTML” for your preview logic; just ensure call sites don’t expect snippets to pass this check.


511-528: Hex dump formatter looks correct and readable.

formatHexView produces a conventional 16-byte-per-line hex + ASCII view with stable offsets and padding; the implementation is clear and matches typical hex-dump output.


return (
<div
role={!!children ? 'button' : undefined}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix: role should always be "button".

The conditional role logic is incorrect. This div always behaves as a button (has onClick, onKeyDown, and keyboard interaction), so it should always have role="button" regardless of whether children are provided.

Apply this diff:

-      role={!!children ? 'button' : undefined}
+      role="button"
📝 Committable suggestion

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

Suggested change
role={!!children ? 'button' : undefined}
role="button"
🤖 Prompt for AI Agents
In packages/bruno-app/src/components/ResponsePane/ResponseDownload/index.js
around line 43, the element conditionally sets role={!!children ? 'button' :
undefined} but the element always behaves like a button (has
onClick/onKeyDown/keyboard interaction); change it to always set role="button"
unconditionally so assistive tech and accessibility tools see the correct role.

Comment on lines +1 to +12
import React, { useState, useEffect, useRef, forwardRef, useCallback, useMemo } from 'react';
import { debounce } from 'lodash';
import styled from 'styled-components';
import { IconDots, IconDownload, IconEraser, IconBookmark, IconCopy } from '@tabler/icons';
import Dropdown from 'components/Dropdown';
import ResponseDownload from '../ResponseDownload';
import ResponseBookmark from '../ResponseBookmark';
import ResponseClear from '../ResponseClear';
import ResponseLayoutToggle, { useResponseLayoutToggle, IconDockToBottom, IconDockToRight } from '../ResponseLayoutToggle';
import ResponseCopy from '../ResponseCopy/index';
import StyledWrapper from '../StyledWrapper';

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

StyledWrapper import path likely incorrect and leaves new wrapper unused

ResponsePaneActions/index.js imports StyledWrapper from ../StyledWrapper, which is the parent ResponsePane wrapper, while this folder also defines ResponsePaneActions/StyledWrapper.js. That new wrapper is currently unused and this component ends up reusing the broader ResponsePane styles.

Recommend either:

  • Switch this import to ./StyledWrapper to use the dedicated wrapper for actions, or
  • Delete ResponsePaneActions/StyledWrapper.js and keep using the shared ../StyledWrapper explicitly.

Right now this is confusing and effectively leaves the new file as dead code.

🤖 Prompt for AI Agents
In packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js
lines 1-12, the file imports StyledWrapper from ../StyledWrapper (the parent
ResponsePane wrapper) while there is a dedicated
ResponsePaneActions/StyledWrapper.js that is unused; update the import to
./StyledWrapper so this component uses its action-specific wrapper, or
alternatively remove ResponsePaneActions/StyledWrapper.js if you prefer the
shared wrapper—prefer switching the import to ./StyledWrapper to avoid dead code
and ensure the actions pane uses its intended styles.

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.

Allow data type selection for response

3 participants