Skip to content

feat: Add browser-style body editor tabs with multi-payload support#5656

Open
divsriv111 wants to merge 10 commits intousebruno:mainfrom
divsriv111:divsriv111/support-tabs-in-request-body
Open

feat: Add browser-style body editor tabs with multi-payload support#5656
divsriv111 wants to merge 10 commits intousebruno:mainfrom
divsriv111:divsriv111/support-tabs-in-request-body

Conversation

@divsriv111
Copy link
Copy Markdown

@divsriv111 divsriv111 commented Sep 29, 2025

Description

Request body editor: browser-style tabs for multiple payloads

This PR enhances Bruno's request body editor by adding a browser-style tab system for all supported body types (JSON, text, XML, etc.). Users can create, rename, switch, and close multiple body tabs; only the active tab is used when sending a request.

Key changes

  • New tabbed interface in the request body editor to manage multiple request bodies.
  • Add new tabs via a "+" button.
  • Each tab represents a separate payload; only the active tab is used when sending a request.
  • Tabs can be renamed (default names: Body 1, Body 2, …) and closed.
  • Request execution now uses the currently active tab’s body and content type.
  • Collection file schema extended to support multiple body entries while preserving backward compatibility:
    • Old collections with a single body are automatically loaded as Body 1.
    • New collections save and reload multiple body tabs correctly.
  • Edge cases handled:
    • Auto-create a blank default tab if all tabs are closed.
    • Prevent duplicate tab names.
  • Improved UI for tab overflow with horizontal scrolling support.

Schema / compatibility

  • Fully backward compatible with existing collection JSON files.
  • New collections persist multiple body payloads per request.
  • Loading an older collection (single body) will map that single body to Body 1 so nothing breaks.

Tests / verification

  • Verified adding, renaming, switching, and closing tabs.
  • Confirmed persistence across reloads (multiple tabs saved and restored).
  • Ensured requests use only the currently active tab when executed.
  • Validated backward compatibility by loading older collection files.

Notes for reviewers

  • Focus review on:
    • Request execution logic (ensuring only the active tab is used).
    • Collection read/write logic for backward compatibility.
    • UI behavior when many tabs are present (overflow/scroll).
    • Edge-case handling (auto-create default tab, duplicate name prevention).
  • Suggested manual test flow:
    1. Open a request and add several tabs with different body types and contents.
    2. Rename tabs and verify names persist after reload.
    3. Close all tabs and confirm a blank Body 1 is created automatically.
    4. Load an older collection with a single body and verify it appears as Body 1.
    5. Send requests with different active tabs and confirm payload/content-type matches active tab.

Demo

Bruno.tabbed.body.demo.mp4

Resolves #5657

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

    • Tabbed request body editor: create/add/rename/duplicate/close/reorder/close‑other tabs with independent formats (JSON, XML, text, SPARQL, file), inline rename, drag reorder, keyboard navigation and accessibility; editor sync on run/save.
  • Schema / Format

    • Request format updated to include body tabs; import/export and language converters updated to read/write tab blocks.
  • Tests

    • Extensive unit and integration tests for UI, persistence, migrations, editor modes, keyboard/ARIA, and request execution.

Copilot AI review requested due to automatic review settings September 29, 2025 04:25
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR enhances Bruno's request body editor by adding browser-style tabs that allow users to manage multiple request body payloads. Users can create, rename, and close tabs with different body types, and only the active tab's payload is used during request execution.

  • Adds tabbed interface for request bodies with create/rename/close functionality
  • Extends collection schema to support multiple body entries with backward compatibility
  • Implements tab content synchronization with request execution logic

Reviewed Changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
packages/bruno-schema/src/collections/index.js Adds bodyTabSchema and bodyTabs field to support multiple body tabs in collection schema
packages/bruno-lang/v2/src/jsonToBru.js Adds serialization support for bodyTabs to .bru file format
packages/bruno-lang/v2/src/bruToJson.js Adds parsing support for body:tab blocks from .bru files
packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js Adds updateRequestBodyTabs Redux action to manage tab state
packages/bruno-app/src/components/RequestPane/RequestBody/index.js Replaces single body editor with tabbed interface and tab management logic
packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/index.js New component providing the tabbed UI with rename/close functionality
packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/StyledWrapper.js Styles for the tabbed interface with scrolling and hover states
packages/bruno-app/src/components/RequestPane/RequestBody/integration.spec.js Integration tests for tab state management and persistence
packages/bruno-app/src/components/RequestPane/RequestBody/index.spec.js Unit tests for RequestBody component with tabbed functionality
packages/bruno-app/src/components/RequestPane/RequestBody/bodyTabs.spec.js Unit tests for bodyTabs Redux functionality
packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/index.spec.js Unit tests for BodyTabs UI component

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment thread packages/bruno-lang/v2/src/jsonToBru.js
Comment thread packages/bruno-lang/v2/src/jsonToBru.js
Comment thread packages/bruno-app/src/components/RequestPane/RequestBody/index.js Outdated
Comment thread packages/bruno-app/src/components/RequestPane/RequestBody/index.js Outdated
Comment thread packages/bruno-app/src/components/RequestPane/RequestBody/index.js
@helloanoop
Copy link
Copy Markdown
Contributor

Thanks for the PR @divsriv111

This definitely makes sense.
This will take some time for review, as DSL changes are involved.

@divsriv111
Copy link
Copy Markdown
Author

hey @helloanoop Just pushed a rebase with master just in case you want to review with the latest changes.

@divsriv111 divsriv111 force-pushed the divsriv111/support-tabs-in-request-body branch from 6456518 to ddf6428 Compare April 5, 2026 16:35
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 5, 2026

Walkthrough

Adds a multi-tab request body editor: UI components and styling for tabbed bodies, local tab state with persistence and Redux sync, BRU grammar/serialization for body:tab, Yup schema validation for tabs, and comprehensive unit/integration tests.

Changes

Cohort / File(s) Summary
Tab UI
packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/StyledWrapper.js, packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/index.js
New styled wrapper and BodyTabs React component providing scrollable tab list, add/rename/close, inline rename editing, keyboard navigation, per-tab context menu, optional drag-reorder (react-dnd), and accessibility attributes.
RequestBody Core
packages/bruno-app/src/components/RequestPane/RequestBody/index.js
Replaced single-editor model with local bodyTabs + activeBodyTab state; syncs edits/mode/tab set to Redux (updateRequestBody, updateRequestBodyMode, updateRequestBodyTabs), debounced persistence, guard refs to avoid feedback loops, and handlers for add/rename/duplicate/close/reorder/close-others; run/save flows updated.
Redux slice
packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
Added updateRequestBodyTabs reducer/action that ensures draft exists and writes draft.request.body.bodyTabs; exported the action.
BRU converters
packages/bruno-lang/v2/src/bruToJson.js, packages/bruno-lang/v2/src/jsonToBru.js
Parse/serialize body:tab blocks ↔ body.bodyTabs; bruToJson coerces id to Number when possible; jsonToBru emits deterministic key ordering and value rendering rules for body:tab.
Schema
packages/bruno-schema/src/collections/index.js
Added bodyTabSchema and extended requestBodySchema to accept optional bodyTabs array (validated tab shape).
Unit tests: BodyTabs
packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/index.spec.js
Comprehensive tests for rendering, add/rename/close, keyboard navigation, accessibility, drag/drop mocks, context menu, and edge cases.
Unit tests: RequestBody
packages/bruno-app/src/components/RequestPane/RequestBody/index.spec.js
Tests for tab initialization/migration, switching, add/rename/duplicate/close flows, mode-specific render swaps (formUrlEncoded, file), and run/save synchronization.
Redux tests / Integration
packages/bruno-app/src/components/RequestPane/RequestBody/bodyTabs.spec.js, packages/bruno-app/src/components/RequestPane/RequestBody/integration.spec.js
Reducer and integration tests covering updateRequestBodyTabs, draft creation, legacy migration, mode switching, request execution with tabs, and error handling.
Exports / API surface
packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/index.js, packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/StyledWrapper.js, packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
Added new component default export BodyTabs, new Styled Wrapper default export, and exported updateRequestBodyTabs action.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant UI as BodyTabs UI
    participant RB as RequestBody (Local)
    participant Redux as Redux Store
    participant Editor as CodeEditor

    User->>UI: Click tab / Add / Rename / Close / Right-click
    UI->>RB: onTabChange / onAddTab / onTabRename / onTabClose / onReorderTab
    RB->>RB: update local bodyTabs & active tab content
    RB->>Redux: dispatch updateRequestBodyTabs / updateRequestBodyMode / updateRequestBody
    Redux->>Redux: persist draft.request.body and bodyTabs
    RB->>Editor: render active tab content & chosen mode
    Editor->>User: show editor with active tab content
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

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

Poem

Tabs awaken where single bodies lay,
Payloads nestle in an ordered array,
Rename, duplicate, switch with a grin —
Many payloads ready to fire and spin,
Requests now carry several lives within. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature: adding browser-style tabbed body editor with multi-payload support, which aligns with all significant changes.
Linked Issues check ✅ Passed The PR fulfills core objectives from #5657: supports multiple body tabs (JSON/XML/text/sparql), persists tabs to collections, maintains backward compatibility, prevents duplicate names, auto-creates default tabs, and enables tab operations (create/rename/close/duplicate/reorder).
Out of Scope Changes check ✅ Passed All changes are within scope: new tab components, Redux state management for tabs, schema validation, BRU language support, and comprehensive test coverage—all directly supporting the multi-tab body editor feature.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

Copy link
Copy Markdown
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/RequestPane/RequestBody/index.js (1)

325-332: ⚠️ Potential issue | 🟡 Minor

Potential null dereference on focusedTab.

If tabs is empty or activeTabUid doesn't match any tab, focusedTab will be undefined, causing focusedTab.uid to throw.

🛡️ Add guard
 const onScroll = (editor) => {
+  if (!focusedTab) return;
   dispatch(
     updateRequestBodyScrollPosition({
       uid: focusedTab.uid,
       scrollY: editor.doc.scrollTop
     })
   );
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js` around
lines 325 - 332, In onScroll, focusedTab may be undefined causing focusedTab.uid
to throw; update the onScroll handler (and any callers) to guard against a
missing focusedTab by returning early or using optional chaining. Specifically,
in the onScroll function that dispatches updateRequestBodyScrollPosition, check
that focusedTab exists (e.g., if (!focusedTab) return) before accessing
focusedTab.uid (or use focusedTab?.uid and handle null case), so dispatch is
only called with a valid uid.
🧹 Nitpick comments (4)
packages/bruno-app/src/components/RequestPane/RequestBody/index.js (3)

60-64: Double initialization of initializeTabContent() is inefficient.

initializeTabContent() is called twice during initial render—once for bodyTabs state and once for activeBodyTab. Consider computing once and reusing:

♻️ Suggested refactor
-  const [bodyTabs, setBodyTabs] = useState(() => initializeTabContent());
-  const [activeBodyTab, setActiveBodyTab] = useState(() => {
-    const tabs = initializeTabContent();
-    return tabs[0]?.id || 1;
-  });
+  const [bodyTabs, setBodyTabs] = useState(initializeTabContent);
+  const [activeBodyTab, setActiveBodyTab] = useState(() => {
+    return bodyTabs[0]?.id || 1;
+  });

Or compute both in a single initializer and split:

const [{ bodyTabs, activeBodyTab }, setTabState] = useState(() => {
  const tabs = initializeTabContent();
  return { bodyTabs: tabs, activeBodyTab: tabs[0]?.id || 1 };
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js` around
lines 60 - 64, initializeTabContent() is being called twice during initial
render for bodyTabs and activeBodyTab; compute it once and reuse the result to
avoid duplicate work by creating a single initializer that calls
initializeTabContent() and sets both bodyTabs and activeBodyTab (or by switching
to one combined state object). Specifically, replace the two useState
initializers with a single initializer that invokes initializeTabContent() once,
then calls setBodyTabs/setActiveBodyTab (or returns an object with bodyTabs and
activeBodyTab) using the tabs[0]?.id || 1 value so initializeTabContent is not
invoked twice.

112-132: Complex useEffect with potential circular dependency.

This effect has bodyTabs in its dependency array and calls setBodyTabs/saveBodyTabsToRedux within. While the guard at line 122 prevents infinite loops in most cases, this pattern is fragile and hard to reason about. If bodyMode and activeTab.bodyType oscillate, re-renders could cascade.

Consider restructuring so that tab bodyType updates flow unidirectionally—either from Redux to local state or vice versa—rather than bidirectionally via effects.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js` around
lines 112 - 132, The useEffect that syncs bodyMode into bodyTabs creates a
fragile bidirectional update between local state and Redux: when bodyMode
changes it maps over bodyTabs and calls setBodyTabs/saveBodyTabsToRedux, yet
bodyTabs is also a dependency which can cause cascades. Replace this effect with
a unidirectional update: move the body-type update logic into the action that
sets bodyMode (or into the handler that triggers bodyMode changes) so that when
bodyMode is changed you compute updatedTabs and call
saveBodyTabsToRedux/setBodyTabs once (remove bodyTabs from the effect dependency
and eliminate setBodyTabs/saveBodyTabsToRedux from any effect); locate symbols
useEffect, bodyMode, RAW_BODY_MODES, getActiveTab, activeBodyTab,
modeSyncSuppressedRef, setBodyTabs, and saveBodyTabsToRedux to apply this
change.

315-323: syncActiveTabBeforeAction dispatches multiple Redux actions synchronously.

This works but results in multiple state updates. If performance becomes a concern, consider batching these dispatches or combining into a single action.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js` around
lines 315 - 323, syncActiveTabBeforeAction currently triggers multiple
synchronous dispatches causing multiple state updates when called from onRun and
onSave; replace this by batching those updates or combining into one thunk so
only a single state update occurs. Fix by creating a new thunk action (e.g.,
runRequestThunk and saveRequestThunk) that performs the
syncActiveTabBeforeAction logic and then dispatches sendRequest(item,
collection.uid) or saveRequest(item.uid, collection.uid) inside the same thunk,
or wrap the existing dispatch calls with react-redux's batch() so onRun and
onSave each call dispatch(runRequestThunk(...)) /
dispatch(saveRequestThunk(...)) (or dispatch(batch(() => { ... }))) to ensure a
single batched update; update references in onRun and onSave to dispatch the new
thunk/batched call.
packages/bruno-app/src/components/RequestPane/RequestBody/index.spec.js (1)

305-326: Request action tests could verify dispatch behavior.

These tests confirm the button is clickable and the textarea value is updated, but don't verify that sendRequest or saveRequest actions were dispatched. Consider spying on the store or mocking the action creators to assert they're called with correct arguments.

As per coding guidelines: "Tests should validate real expected output and observable behaviour."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.spec.js`
around lines 305 - 326, The tests in RequestBody spec assert UI clicks and
textarea value but don't verify that the request actions are dispatched; update
the two tests in the "Request Actions with Tabs" suite to spy/mock the Redux
dispatch or the action creators (sendRequest and saveRequest) and assert they
are called with the expected payload when Run is clicked. Locate the RequestBody
component tests that query 'run-button' and 'code-editor-textarea' and replace
or augment those assertions by injecting a mocked store or jest.spyOn the action
creators (sendRequest, saveRequest) and expect those spies to have been called
with the request body content (e.g., '{"ready":"to run"}') and correct
item/collection identifiers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js`:
- Around line 231-241: When recreating the last tab in the block that builds
newBlankTab, validate bodyMode against RAW_BODY_MODES (same approach as
handleAddTab) instead of using bodyMode || 'json'; compute a validated bodyType
(e.g., RAW_BODY_MODES.includes(bodyMode) ? bodyMode : 'json') and set
newBlankTab.bodyType to that value, then continue to call setBodyTabs,
setActiveBodyTab, and saveBodyTabsToRedux with the newBlankTab.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.spec.js`:
- Around line 72-76: The mock state used in the test is missing the tabs.tabs
array which the component reads via useSelector (state.tabs.tabs) and then calls
find; update the test's mock state object in RequestBody/index.spec.js to
include a tabs array (e.g., a tab object whose uid matches 'item-1') so that the
component's find call operates on an array not undefined; locate the mock where
tabs: { activeTabUid: 'item-1' } is defined and add a tabs: [...] entry
containing the matching tab item.

---

Outside diff comments:
In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js`:
- Around line 325-332: In onScroll, focusedTab may be undefined causing
focusedTab.uid to throw; update the onScroll handler (and any callers) to guard
against a missing focusedTab by returning early or using optional chaining.
Specifically, in the onScroll function that dispatches
updateRequestBodyScrollPosition, check that focusedTab exists (e.g., if
(!focusedTab) return) before accessing focusedTab.uid (or use focusedTab?.uid
and handle null case), so dispatch is only called with a valid uid.

---

Nitpick comments:
In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js`:
- Around line 60-64: initializeTabContent() is being called twice during initial
render for bodyTabs and activeBodyTab; compute it once and reuse the result to
avoid duplicate work by creating a single initializer that calls
initializeTabContent() and sets both bodyTabs and activeBodyTab (or by switching
to one combined state object). Specifically, replace the two useState
initializers with a single initializer that invokes initializeTabContent() once,
then calls setBodyTabs/setActiveBodyTab (or returns an object with bodyTabs and
activeBodyTab) using the tabs[0]?.id || 1 value so initializeTabContent is not
invoked twice.
- Around line 112-132: The useEffect that syncs bodyMode into bodyTabs creates a
fragile bidirectional update between local state and Redux: when bodyMode
changes it maps over bodyTabs and calls setBodyTabs/saveBodyTabsToRedux, yet
bodyTabs is also a dependency which can cause cascades. Replace this effect with
a unidirectional update: move the body-type update logic into the action that
sets bodyMode (or into the handler that triggers bodyMode changes) so that when
bodyMode is changed you compute updatedTabs and call
saveBodyTabsToRedux/setBodyTabs once (remove bodyTabs from the effect dependency
and eliminate setBodyTabs/saveBodyTabsToRedux from any effect); locate symbols
useEffect, bodyMode, RAW_BODY_MODES, getActiveTab, activeBodyTab,
modeSyncSuppressedRef, setBodyTabs, and saveBodyTabsToRedux to apply this
change.
- Around line 315-323: syncActiveTabBeforeAction currently triggers multiple
synchronous dispatches causing multiple state updates when called from onRun and
onSave; replace this by batching those updates or combining into one thunk so
only a single state update occurs. Fix by creating a new thunk action (e.g.,
runRequestThunk and saveRequestThunk) that performs the
syncActiveTabBeforeAction logic and then dispatches sendRequest(item,
collection.uid) or saveRequest(item.uid, collection.uid) inside the same thunk,
or wrap the existing dispatch calls with react-redux's batch() so onRun and
onSave each call dispatch(runRequestThunk(...)) /
dispatch(saveRequestThunk(...)) (or dispatch(batch(() => { ... }))) to ensure a
single batched update; update references in onRun and onSave to dispatch the new
thunk/batched call.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.spec.js`:
- Around line 305-326: The tests in RequestBody spec assert UI clicks and
textarea value but don't verify that the request actions are dispatched; update
the two tests in the "Request Actions with Tabs" suite to spy/mock the Redux
dispatch or the action creators (sendRequest and saveRequest) and assert they
are called with the expected payload when Run is clicked. Locate the RequestBody
component tests that query 'run-button' and 'code-editor-textarea' and replace
or augment those assertions by injecting a mocked store or jest.spyOn the action
creators (sendRequest, saveRequest) and expect those spies to have been called
with the request body content (e.g., '{"ready":"to run"}') and correct
item/collection identifiers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: caf1a7f0-8f1d-42a0-840d-d327f99a1f53

📥 Commits

Reviewing files that changed from the base of the PR and between fabba4d and ddf6428.

📒 Files selected for processing (11)
  • packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/StyledWrapper.js
  • packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/index.js
  • packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/index.spec.js
  • packages/bruno-app/src/components/RequestPane/RequestBody/bodyTabs.spec.js
  • packages/bruno-app/src/components/RequestPane/RequestBody/index.js
  • packages/bruno-app/src/components/RequestPane/RequestBody/index.spec.js
  • packages/bruno-app/src/components/RequestPane/RequestBody/integration.spec.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
  • packages/bruno-lang/v2/src/bruToJson.js
  • packages/bruno-lang/v2/src/jsonToBru.js
  • packages/bruno-schema/src/collections/index.js
👮 Files not reviewed due to content moderation or server errors (8)
  • packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/StyledWrapper.js
  • packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/index.js
  • packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/index.spec.js
  • packages/bruno-lang/v2/src/bruToJson.js
  • packages/bruno-schema/src/collections/index.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
  • packages/bruno-lang/v2/src/jsonToBru.js
  • packages/bruno-app/src/components/RequestPane/RequestBody/bodyTabs.spec.js

Comment thread packages/bruno-app/src/components/RequestPane/RequestBody/index.js
Copy link
Copy Markdown
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.

♻️ Duplicate comments (2)
packages/bruno-app/src/components/RequestPane/RequestBody/index.spec.js (1)

85-89: ⚠️ Potential issue | 🟡 Minor

Mock state missing tabs.tabs array.

The component accesses state.tabs.tabs via useSelector (line 36 in index.js), but the mock only provides activeTabUid. While lodash's find handles undefined gracefully, the mock should match actual Redux state structure for test fidelity.

🐛 Add tabs array to mock
     tabs: {
+      tabs: [{ uid: 'item-1', requestBodyScrollPosition: 0 }],
       activeTabUid: 'item-1'
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.spec.js`
around lines 85 - 89, The mock state for the test is missing the tabs.tabs array
expected by the component; update the test's mock state where tabs is defined
(currently tabs: { activeTabUid: 'item-1' }) to include a tabs array (e.g.,
tabs: { activeTabUid: 'item-1', tabs: [ { uid: 'item-1', ... } ] }) so
useSelector in the component (which reads state.tabs.tabs) gets the same shape
as real Redux state and lodash.find receives an array instead of undefined;
ensure the inserted tab object has a uid matching activeTabUid to keep the test
semantics.
packages/bruno-app/src/components/RequestPane/RequestBody/index.js (1)

220-230: ⚠️ Potential issue | 🟡 Minor

Inconsistent bodyType fallback when closing last tab.

At line 225, bodyType: bodyMode || 'json' doesn't validate against RAW_BODY_MODES, unlike handleAddTab (line 185) which does. If bodyMode is 'formUrlEncoded', the new blank tab will have an invalid bodyType.

🐛 Proposed fix
   if (bodyTabs.length === 1) {
+    const fallbackMode = bodyMode && RAW_BODY_MODES.includes(bodyMode) ? bodyMode : 'json';
     const newBlankTab = {
       id: Math.max(0, ...bodyTabs.map((tab) => tab.id)) + 1,
       name: getNextTabName([]),
       bodyContent: '',
-      bodyType: bodyMode || 'json'
+      bodyType: fallbackMode
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js` around
lines 220 - 230, When closing the last body tab you set bodyType using "bodyMode
|| 'json'" which doesn't validate allowed modes; update the creation of
newBlankTab in the block that handles bodyTabs.length === 1 to validate bodyMode
against RAW_BODY_MODES (same logic as handleAddTab) and fall back to 'json' if
bodyMode is not included in RAW_BODY_MODES; modify the assignment for bodyType
in the newBlankTab object so the value is either a valid RAW_BODY_MODES entry or
'json' before calling setBodyTabs, setActiveBodyTab, and saveBodyTabsToRedux.
🧹 Nitpick comments (5)
packages/bruno-app/src/components/RequestPane/RequestBody/index.js (2)

68-70: Consider using lazy initializer for useState instead of useMemo.

The useMemo with empty deps computes initialTabs once, then it's used to initialize useState. The idiomatic React pattern is a lazy initializer:

♻️ Optional: Use lazy initializer
-  const initialTabs = useMemo(() => initializeTabContent(), []);
-  const [bodyTabs, setBodyTabs] = useState(initialTabs);
-  const [activeBodyTab, setActiveBodyTab] = useState(initialTabs[0]?.id || 1);
+  const [bodyTabs, setBodyTabs] = useState(() => initializeTabContent());
+  const [activeBodyTab, setActiveBodyTab] = useState(() => {
+    const tabs = initializeTabContent();
+    return tabs[0]?.id || 1;
+  });

This avoids the exhaustive-deps lint warning and is a more standard pattern for initial state that depends on props.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js` around
lines 68 - 70, The code currently computes initialTabs via useMemo and then uses
it to initialize state; instead replace that with lazy initializers on the
useState calls so the initializer function initializeTabContent is invoked only
once at mount: call useState(() => initializeTabContent()) for bodyTabs (update
references to bodyTabs and setBodyTabs) and initialize activeBodyTab with a lazy
initializer that derives the id from the same initializeTabContent() result
(e.g. useState(() => initializeTabContent()[0]?.id || 1)) and remove the
separate useMemo and initialTabs variable.

126-165: Complex sync logic would benefit from clarifying comments.

The useEffect handles several scenarios: external body mode changes, tab switches, and content synchronization. The ref-based tracking (lastChangeSourceRef, lastSyncedTabIdRef, etc.) prevents feedback loops but makes the flow hard to follow.

Consider adding brief inline comments explaining when each branch triggers:

// Branch 1: External bodyMode change → update active tab's bodyType
if (bodyModeIsRaw && activeTabMode && activeTab.bodyType !== bodyMode && lastChangeSourceRef.current !== 'tab-switch') {
  ...
}

// Branch 2: Tab switch → sync bodyMode to match active tab's bodyType  
if (activeTabMode && bodyModeIsRaw && bodyMode !== activeTabMode) {
  ...
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js` around
lines 126 - 165, The useEffect sync logic (useEffect, getActiveTab,
lastChangeSourceRef, lastSyncedTabIdRef, lastSyncedContentRef) is hard to
follow—add concise inline comments above each major branch to explain intent:
before the first if that updates bodyTabs/saveBodyTabsToRedux explain "Branch 1:
external bodyMode change → update active tab's bodyType (avoid when tab-switch
initiated)"; before the next if that dispatches updateRequestBodyMode explain
"Branch 2: tab switch → sync global bodyMode to active tab's bodyType (mark
lastChangeSourceRef to prevent feedback)"; and before the final block that
dispatches updateRequestBody explain "Branch 3: content sync → push active tab
bodyContent to store only when content or tab id changed (use
lastSyncedTabIdRef/lastSyncedContentRef)". Keep comments short and place them
directly above the corresponding if/blocks (referencing activeBodyTab, bodyMode,
bodyTabs, dispatch, updateRequestBodyMode, updateRequestBody) so future readers
can quickly map behavior to the ref guards.
packages/bruno-app/src/components/RequestPane/RequestBody/index.spec.js (2)

200-244: Good rename test coverage; consider adding Escape key cancel test.

The rename tests cover double-click entry, Enter submission, and duplicate name handling. For completeness, consider testing:

  • Escape key cancels rename (restores original name)
  • Empty/whitespace-only rename is rejected
♻️ Optional: Add Escape cancel test
it('should cancel rename on Escape key', async () => {
  renderWithProviders(<RequestBody item={mockItem} collection={mockCollection} />);

  const tabElement = screen.getByText('Body 1');
  fireEvent.doubleClick(tabElement);

  const input = screen.getByDisplayValue('Body 1');
  fireEvent.change(input, { target: { value: 'Changed Name' } });
  fireEvent.keyDown(input, { key: 'Escape' });

  await waitFor(() => {
    expect(screen.getByText('Body 1')).toBeInTheDocument();
    expect(screen.queryByText('Changed Name')).not.toBeInTheDocument();
  });
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.spec.js`
around lines 200 - 244, Add a new unit test in RequestBody's index.spec.js to
verify that pressing Escape cancels an in-progress rename: render the
RequestBody (use renderWithProviders with mockItem), locate the tab by text
(screen.getByText('Body 1')), trigger edit with fireEvent.doubleClick, change
the input value (screen.getByDisplayValue('Body 1') → fireEvent.change to
'Changed Name'), simulate pressing Escape with fireEvent.keyDown(input, { key:
'Escape' }), and assert via waitFor that the original name ('Body 1') is still
in the document and the changed name is not (screen.queryByText('Changed Name')
should be null). Include this as a new it('should cancel rename on Escape key',
...) test near the existing Tab Renaming tests.

273-283: Test clicks a hidden close button.

When there's only one tab, the close button has display: none (line 197 in BodyTabs). This test still passes because fireEvent ignores visibility, but it tests an unreachable UI path.

If the intent is to test closing the last tab, use the context menu path instead, or update BodyTabs to allow this via button.

♻️ Alternative: Test via context menu or remove

Option 1: Remove this test since users can't click a hidden button.

Option 2: Trigger close via context menu (requires updating the mock or exposing context menu interaction).

Option 3: If closing last tab via button should be allowed, update BodyTabs to not hide the button.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.spec.js`
around lines 273 - 283, The test in RequestBody/index.spec.js simulates clicking
the hidden "Close tab" button (getByTitle('Close tab')) which is not visible
when there's only one tab because BodyTabs hides it; update the test to exercise
a reachable user path: either remove the test, or replace the fireEvent.click on
the hidden button with a context-menu close action by opening the tab context
menu and triggering the "Close tab" command (mock the context menu handler used
by BodyTabs or call the same close handler exposed by RequestBody/BodyTabs),
ensuring you assert that "Body 1" is recreated and code-editor textarea is
empty; reference the RequestBody component and BodyTabs' context-menu/close
handler to locate and wire the correct interaction.
packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/index.js (1)

145-159: Context menu "Close" allows closing the last tab while the button is hidden.

The close button is hidden when tabs.length === 1 (line 197), but the context menu "Close" item has no disabled condition. Users can still close the last tab via right-click.

If this is intentional, no change needed. If you want consistent behavior, disable "Close" when it's the only tab:

♻️ Optional: Disable close in context menu for last tab
     {
       id: 'close',
       label: 'Close',
+      disabled: tabs.length <= 1,
       onClick: () => {
         if (onTabClose && contextMenuTabId != null) onTabClose(contextMenuTabId);
       }
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/index.js`
around lines 145 - 159, The context-menu "Close" item currently allows closing
the last tab because it lacks a disabled check; update the menu item with id
'close' (the entry that calls onTabClose with contextMenuTabId) to be disabled
when tabs.length <= 1 (same rule used for the 'close-others' item), or
alternatively add a guard before calling onTabClose to skip/return when
tabs.length <= 1; reference the 'onTabClose' callback, 'contextMenuTabId', and
'tabs.length' when implementing the check so the behavior matches the hidden
close button.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js`:
- Around line 220-230: When closing the last body tab you set bodyType using
"bodyMode || 'json'" which doesn't validate allowed modes; update the creation
of newBlankTab in the block that handles bodyTabs.length === 1 to validate
bodyMode against RAW_BODY_MODES (same logic as handleAddTab) and fall back to
'json' if bodyMode is not included in RAW_BODY_MODES; modify the assignment for
bodyType in the newBlankTab object so the value is either a valid RAW_BODY_MODES
entry or 'json' before calling setBodyTabs, setActiveBodyTab, and
saveBodyTabsToRedux.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.spec.js`:
- Around line 85-89: The mock state for the test is missing the tabs.tabs array
expected by the component; update the test's mock state where tabs is defined
(currently tabs: { activeTabUid: 'item-1' }) to include a tabs array (e.g.,
tabs: { activeTabUid: 'item-1', tabs: [ { uid: 'item-1', ... } ] }) so
useSelector in the component (which reads state.tabs.tabs) gets the same shape
as real Redux state and lodash.find receives an array instead of undefined;
ensure the inserted tab object has a uid matching activeTabUid to keep the test
semantics.

---

Nitpick comments:
In `@packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/index.js`:
- Around line 145-159: The context-menu "Close" item currently allows closing
the last tab because it lacks a disabled check; update the menu item with id
'close' (the entry that calls onTabClose with contextMenuTabId) to be disabled
when tabs.length <= 1 (same rule used for the 'close-others' item), or
alternatively add a guard before calling onTabClose to skip/return when
tabs.length <= 1; reference the 'onTabClose' callback, 'contextMenuTabId', and
'tabs.length' when implementing the check so the behavior matches the hidden
close button.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js`:
- Around line 68-70: The code currently computes initialTabs via useMemo and
then uses it to initialize state; instead replace that with lazy initializers on
the useState calls so the initializer function initializeTabContent is invoked
only once at mount: call useState(() => initializeTabContent()) for bodyTabs
(update references to bodyTabs and setBodyTabs) and initialize activeBodyTab
with a lazy initializer that derives the id from the same initializeTabContent()
result (e.g. useState(() => initializeTabContent()[0]?.id || 1)) and remove the
separate useMemo and initialTabs variable.
- Around line 126-165: The useEffect sync logic (useEffect, getActiveTab,
lastChangeSourceRef, lastSyncedTabIdRef, lastSyncedContentRef) is hard to
follow—add concise inline comments above each major branch to explain intent:
before the first if that updates bodyTabs/saveBodyTabsToRedux explain "Branch 1:
external bodyMode change → update active tab's bodyType (avoid when tab-switch
initiated)"; before the next if that dispatches updateRequestBodyMode explain
"Branch 2: tab switch → sync global bodyMode to active tab's bodyType (mark
lastChangeSourceRef to prevent feedback)"; and before the final block that
dispatches updateRequestBody explain "Branch 3: content sync → push active tab
bodyContent to store only when content or tab id changed (use
lastSyncedTabIdRef/lastSyncedContentRef)". Keep comments short and place them
directly above the corresponding if/blocks (referencing activeBodyTab, bodyMode,
bodyTabs, dispatch, updateRequestBodyMode, updateRequestBody) so future readers
can quickly map behavior to the ref guards.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.spec.js`:
- Around line 200-244: Add a new unit test in RequestBody's index.spec.js to
verify that pressing Escape cancels an in-progress rename: render the
RequestBody (use renderWithProviders with mockItem), locate the tab by text
(screen.getByText('Body 1')), trigger edit with fireEvent.doubleClick, change
the input value (screen.getByDisplayValue('Body 1') → fireEvent.change to
'Changed Name'), simulate pressing Escape with fireEvent.keyDown(input, { key:
'Escape' }), and assert via waitFor that the original name ('Body 1') is still
in the document and the changed name is not (screen.queryByText('Changed Name')
should be null). Include this as a new it('should cancel rename on Escape key',
...) test near the existing Tab Renaming tests.
- Around line 273-283: The test in RequestBody/index.spec.js simulates clicking
the hidden "Close tab" button (getByTitle('Close tab')) which is not visible
when there's only one tab because BodyTabs hides it; update the test to exercise
a reachable user path: either remove the test, or replace the fireEvent.click on
the hidden button with a context-menu close action by opening the tab context
menu and triggering the "Close tab" command (mock the context menu handler used
by BodyTabs or call the same close handler exposed by RequestBody/BodyTabs),
ensuring you assert that "Body 1" is recreated and code-editor textarea is
empty; reference the RequestBody component and BodyTabs' context-menu/close
handler to locate and wire the correct interaction.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 85197787-dac0-42de-8045-e5a414fd282a

📥 Commits

Reviewing files that changed from the base of the PR and between ddf6428 and a42cef9.

📒 Files selected for processing (5)
  • packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/StyledWrapper.js
  • packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/index.js
  • packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/index.spec.js
  • packages/bruno-app/src/components/RequestPane/RequestBody/index.js
  • packages/bruno-app/src/components/RequestPane/RequestBody/index.spec.js
✅ Files skipped from review due to trivial changes (2)
  • packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/StyledWrapper.js
  • packages/bruno-app/src/components/RequestPane/RequestBody/BodyTabs/index.spec.js

Copy link
Copy Markdown
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/components/RequestPane/RequestBody/index.spec.js (1)

319-344: Assertions don't verify the claimed Redux sync behavior.

Both tests claim to validate Redux state updates but only assert on UI element presence. The first test checks state is defined (always true), and the second only verifies the textarea value — neither confirms actual Redux state changes.

Consider verifying the Redux state slice or dispatched actions if testing Redux sync is the intent.

♻️ Suggested stronger assertions
   it('should handle run action with tabbed content and update Redux state', async () => {
     const store = createMockStore();
     renderWithProviders(<RequestBody item={mockItem} collection={mockCollection} />, store);

     const runButton = screen.getByTestId('run-button');
     fireEvent.click(runButton);

-    expect(runButton).toBeInTheDocument();
-    const state = store.getState();
-    expect(state).toBeDefined();
+    // Verify button click occurred (sanity check)
+    expect(runButton).toBeInTheDocument();
+    // Optionally verify Redux state if sync behavior is critical
+    const state = store.getState();
+    const collection = state.collections.collections.find((c) => c.uid === 'collection-1');
+    // Add assertion on expected draft/body state if this test is meant to verify Redux sync
   });

   it('should sync active tab content to Redux before running request', async () => {
     const store = createMockStore();
     renderWithProviders(<RequestBody item={mockItem} collection={mockCollection} />, store);

     const textarea = screen.getByTestId('code-editor-textarea');
     fireEvent.change(textarea, { target: { value: '{"ready": "to run"}' } });

     const runButton = screen.getByTestId('run-button');
     fireEvent.click(runButton);

-    expect(textarea).toHaveValue('{"ready": "to run"}');
+    expect(textarea).toHaveValue('{"ready": "to run"}');
+    // Consider verifying the Redux draft body matches the edited content
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.spec.js`
around lines 319 - 344, The tests for RequestBody (renderWithProviders +
createMockStore) claim to verify Redux syncing but never assert on the store;
update these specs to inspect the mock store instead of only UI elements: after
clicking the element with test id 'run-button' (and after changing
'code-editor-textarea'), call the mock store's API (e.g., store.getActions() or
store.getState()) to assert that the expected action was dispatched or that the
specific Redux slice (e.g., the request/activeTab or request.body field) was
updated to '{"ready": "to run"}'; reference RequestBody, run-button,
code-editor-textarea, createMockStore and store.getState/getActions to locate
where to add those stronger assertions.
packages/bruno-app/src/components/RequestPane/RequestBody/index.js (1)

126-165: Consider extracting this sync logic to a custom hook.

This useEffect manages three concerns: syncing tab bodyType to Redux mode, syncing Redux mode to tabs, and syncing content. While the ref-based guards prevent loops correctly, the cognitive load is high.

If this grows further, extracting to a useTabReduxSync hook would improve testability and readability. Not urgent given it works, but worth noting for maintainability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js` around
lines 126 - 165, The large useEffect in RequestBody handles three separate sync
responsibilities and should be extracted into a custom hook (e.g.,
useTabReduxSync) to improve readability and testability: create useTabReduxSync
that encapsulates the logic currently inside the useEffect (including calls to
getActiveTab, checks against RAW_BODY_MODES, updates via
setBodyTabs/saveBodyTabsToRedux, and dispatching updateRequestBodyMode and
updateRequestBody) and accepts needed inputs (bodyTabs, activeBodyTab, bodyMode,
item.uid, collection.uid, dispatch, setBodyTabs, saveBodyTabsToRedux and the
refs lastChangeSourceRef/lastSyncedTabIdRef/lastSyncedContentRef) then replace
the useEffect with a single call to useTabReduxSync in RequestBody; keep all
guard semantics (lastChangeSourceRef checks, lastSynced* refs) and preserve the
exact update behavior for bodyType and bodyContent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js`:
- Around line 263-280: The duplicate tab naming in handleDuplicateTab creates
identical names like "X (copy)" on multiple duplicates; update
handleDuplicateTab to generate a unique name before inserting by deriving
baseName from tabToDuplicate.name and scanning bodyTabs for existing names that
match the baseName + " (copy)" or " (copy N)" pattern, compute the next
available integer suffix (use " (copy)" for the first duplicate and " (copy 2)",
" (copy 3)", etc. thereafter), set newTab.name to that unique value, then
proceed to setBodyTabs(newTabs), setActiveBodyTab(newTabId), and
saveBodyTabsToRedux(newTabs).

---

Nitpick comments:
In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js`:
- Around line 126-165: The large useEffect in RequestBody handles three separate
sync responsibilities and should be extracted into a custom hook (e.g.,
useTabReduxSync) to improve readability and testability: create useTabReduxSync
that encapsulates the logic currently inside the useEffect (including calls to
getActiveTab, checks against RAW_BODY_MODES, updates via
setBodyTabs/saveBodyTabsToRedux, and dispatching updateRequestBodyMode and
updateRequestBody) and accepts needed inputs (bodyTabs, activeBodyTab, bodyMode,
item.uid, collection.uid, dispatch, setBodyTabs, saveBodyTabsToRedux and the
refs lastChangeSourceRef/lastSyncedTabIdRef/lastSyncedContentRef) then replace
the useEffect with a single call to useTabReduxSync in RequestBody; keep all
guard semantics (lastChangeSourceRef checks, lastSynced* refs) and preserve the
exact update behavior for bodyType and bodyContent.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.spec.js`:
- Around line 319-344: The tests for RequestBody (renderWithProviders +
createMockStore) claim to verify Redux syncing but never assert on the store;
update these specs to inspect the mock store instead of only UI elements: after
clicking the element with test id 'run-button' (and after changing
'code-editor-textarea'), call the mock store's API (e.g., store.getActions() or
store.getState()) to assert that the expected action was dispatched or that the
specific Redux slice (e.g., the request/activeTab or request.body field) was
updated to '{"ready": "to run"}'; reference RequestBody, run-button,
code-editor-textarea, createMockStore and store.getState/getActions to locate
where to add those stronger assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7bd07aff-0a79-4107-803c-178d870cef75

📥 Commits

Reviewing files that changed from the base of the PR and between a42cef9 and 7238330.

📒 Files selected for processing (2)
  • packages/bruno-app/src/components/RequestPane/RequestBody/index.js
  • packages/bruno-app/src/components/RequestPane/RequestBody/index.spec.js

Comment thread packages/bruno-app/src/components/RequestPane/RequestBody/index.js
Copy link
Copy Markdown
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 (3)
packages/bruno-app/src/components/RequestPane/RequestBody/index.js (3)

126-165: Complex sync logic works but could benefit from inline comments.

The useEffect handles multiple sync scenarios (external bodyMode change vs tab-switch, content persistence) using refs as guards. The logic is correct, but the interactions between lastChangeSourceRef, lastSyncedTabIdRef, and lastSyncedContentRef are non-obvious. A brief comment block explaining the state machine would help future maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js` around
lines 126 - 165, Add a short explanatory comment block inside the useEffect
above the sync logic describing the state machine and the purpose of the refs:
explain that lastChangeSourceRef distinguishes programmatic tab-switch updates
from user-driven bodyMode changes, lastSyncedTabIdRef and lastSyncedContentRef
prevent duplicate dispatches of updateRequestBody, and how
bodyTabs/activeBodyTab/bodyMode/RAW_BODY_MODES interact to decide when to call
setBodyTabs/saveBodyTabsToRedux or dispatch
updateRequestBodyMode/updateRequestBody; reference the key symbols useEffect,
lastChangeSourceRef, lastSyncedTabIdRef, lastSyncedContentRef, bodyTabs,
activeBodyTab, bodyMode, RAW_BODY_MODES, setBodyTabs, saveBodyTabsToRedux,
updateRequestBodyMode, updateRequestBody so future maintainers can quickly
understand each branch's intent.

117-120: Stale closure: bodyMode not in useMemo dependencies.

saveBodyTabsToRedux uses bodyMode at line 104 for its fallback logic. Since bodyMode isn't in the dependency array, when bodyMode changes (while item.uid/collection.uid stay the same), the debounced function will capture stale bodyMode values.

♻️ Proposed fix
 const debouncedSaveBodyTabs = useMemo(
   () => debounce((tabs) => saveBodyTabsToRedux(tabs), 300),
-  [item.uid, collection.uid]
+  [item.uid, collection.uid, bodyMode]
 );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js` around
lines 117 - 120, The debouncedSaveBodyTabs closure captures a stale bodyMode
because bodyMode is not listed in the useMemo deps; update the useMemo that
creates debouncedSaveBodyTabs to include bodyMode (i.e., [item.uid,
collection.uid, bodyMode]) so debouncedSaveBodyTabs recreates when bodyMode
changes, ensuring saveBodyTabsToRedux (which reads bodyMode) uses the current
value; alternatively, ensure saveBodyTabsToRedux is a stable callback that reads
bodyMode from up-to-date state, but the simplest fix is adding bodyMode to the
dependency array of the useMemo that defines debouncedSaveBodyTabs.

316-350: Consider canceling pending debounced saves before sync.

syncActiveTabBeforeAction calls saveBodyTabsToRedux directly, but if debouncedSaveBodyTabs has a pending flush from recent edits, you could get out-of-order dispatches. Flush or cancel the debounce first.

🛡️ Optional safeguard
 const syncActiveTabBeforeAction = () => {
+  debouncedSaveBodyTabs.cancel();
   const currentActiveTab = bodyTabs.find((tab) => tab.id === activeBodyTab);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js` around
lines 316 - 350, syncActiveTabBeforeAction can trigger saveBodyTabsToRedux while
a pending debounced save (debouncedSaveBodyTabs) is queued, causing out-of-order
dispatches; before calling saveBodyTabsToRedux, ensure any pending debounce is
flushed or canceled. Modify syncActiveTabBeforeAction to call
debouncedSaveBodyTabs.flush() (or debouncedSaveBodyTabs.cancel() then call
saveBodyTabsToRedux immediately) prior to saveBodyTabsToRedux, keeping the
existing updates to updateRequestBodyMode/updateRequestBody and
lastSyncedTabIdRef/lastSyncedContentRef intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js`:
- Around line 386-388: The JSX in RequestBody currently uses the Tailwind color
class text-gray-500; replace that with a themed styled-class: add a
.placeholder-message rule in StyledWrapper.js that sets color: ${(props) =>
props.theme.colors.text.muted} and then update the JSX div in RequestBody (the
placeholder message element) to remove text-gray-500 and use className
"placeholder-message" alongside existing layout classes (e.g., keep "text-center
py-8"). Ensure the styled wrapper is applied to the component so the theme prop
is available.

---

Nitpick comments:
In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js`:
- Around line 126-165: Add a short explanatory comment block inside the
useEffect above the sync logic describing the state machine and the purpose of
the refs: explain that lastChangeSourceRef distinguishes programmatic tab-switch
updates from user-driven bodyMode changes, lastSyncedTabIdRef and
lastSyncedContentRef prevent duplicate dispatches of updateRequestBody, and how
bodyTabs/activeBodyTab/bodyMode/RAW_BODY_MODES interact to decide when to call
setBodyTabs/saveBodyTabsToRedux or dispatch
updateRequestBodyMode/updateRequestBody; reference the key symbols useEffect,
lastChangeSourceRef, lastSyncedTabIdRef, lastSyncedContentRef, bodyTabs,
activeBodyTab, bodyMode, RAW_BODY_MODES, setBodyTabs, saveBodyTabsToRedux,
updateRequestBodyMode, updateRequestBody so future maintainers can quickly
understand each branch's intent.
- Around line 117-120: The debouncedSaveBodyTabs closure captures a stale
bodyMode because bodyMode is not listed in the useMemo deps; update the useMemo
that creates debouncedSaveBodyTabs to include bodyMode (i.e., [item.uid,
collection.uid, bodyMode]) so debouncedSaveBodyTabs recreates when bodyMode
changes, ensuring saveBodyTabsToRedux (which reads bodyMode) uses the current
value; alternatively, ensure saveBodyTabsToRedux is a stable callback that reads
bodyMode from up-to-date state, but the simplest fix is adding bodyMode to the
dependency array of the useMemo that defines debouncedSaveBodyTabs.
- Around line 316-350: syncActiveTabBeforeAction can trigger saveBodyTabsToRedux
while a pending debounced save (debouncedSaveBodyTabs) is queued, causing
out-of-order dispatches; before calling saveBodyTabsToRedux, ensure any pending
debounce is flushed or canceled. Modify syncActiveTabBeforeAction to call
debouncedSaveBodyTabs.flush() (or debouncedSaveBodyTabs.cancel() then call
saveBodyTabsToRedux immediately) prior to saveBodyTabsToRedux, keeping the
existing updates to updateRequestBodyMode/updateRequestBody and
lastSyncedTabIdRef/lastSyncedContentRef intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a41d3cb7-2b9c-4539-be08-6c5eb10246d4

📥 Commits

Reviewing files that changed from the base of the PR and between 7238330 and 025d76f.

📒 Files selected for processing (1)
  • packages/bruno-app/src/components/RequestPane/RequestBody/index.js

Comment on lines +386 to +388
<div className="text-center py-8 text-gray-500">
Select a body type from the dropdown above to start adding content.
</div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Tailwind color class violates coding guidelines.

text-gray-500 defines color via Tailwind, but guidelines specify Tailwind should only be used for layout and colors should come from styled components' theme prop.

🎨 Proposed fix using styled component

Add a styled element in StyledWrapper.js:

.placeholder-message {
  color: ${(props) => props.theme.colors.text.muted};
}

Then update the JSX:

-        <div className="text-center py-8 text-gray-500">
+        <div className="text-center py-8 placeholder-message">
           Select a body type from the dropdown above to start adding content.
         </div>

As per coding guidelines: "Tailwind classes are used specifically for layout-based styles" and "Tailwind classes shouldn't define colors."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/RequestBody/index.js` around
lines 386 - 388, The JSX in RequestBody currently uses the Tailwind color class
text-gray-500; replace that with a themed styled-class: add a
.placeholder-message rule in StyledWrapper.js that sets color: ${(props) =>
props.theme.colors.text.muted} and then update the JSX div in RequestBody (the
placeholder message element) to remove text-gray-500 and use className
"placeholder-message" alongside existing layout classes (e.g., keep "text-center
py-8"). Ensure the styled wrapper is applied to the component so the theme prop
is available.

@divsriv111
Copy link
Copy Markdown
Author

divsriv111 commented Apr 5, 2026

Hi @helloanoop, @lohit-bruno , @naman-bruno , @bijin-bruno – I have just rebased this PR with the latest master branch to ensure a clean review against the current codebase.

As noted in the issue discussion [#5657], there is significant demand for this feature, and it addresses a frequent pain point for developers and QA. Because this area of the project is evolving quickly, I am prioritizing keeping the PR up to date; however, I would appreciate a review at your earliest convenience to prevent further merge conflicts.

Thanks for your time!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhanced request body editor: multi-tabbed payloads for request testing & comparisons

5 participants