Skip to content

feat: add description column in Headers, VarsTable, and other components to allow multi-line descriptions#7129

Open
Pragadesh-45 wants to merge 2 commits intousebruno:mainfrom
Pragadesh-45:feat/descriptions
Open

feat: add description column in Headers, VarsTable, and other components to allow multi-line descriptions#7129
Pragadesh-45 wants to merge 2 commits intousebruno:mainfrom
Pragadesh-45:feat/descriptions

Conversation

@Pragadesh-45
Copy link
Contributor

@Pragadesh-45 Pragadesh-45 commented Feb 13, 2026

Description

  • Introduced a description column in Headers, VarsTable, and other components to allow multi-line descriptions.
  • Added toggle buttons to show/hide the description column in relevant tables.
  • Updated EditableTable component to support dynamic row addition with customizable labels.
  • Enhanced UI for better user experience with consistent button styles and spacing adjustments.

Contribution Checklist:

  • I've used AI significantly to create this pull request
  • 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.

Summary by CodeRabbit

  • New Features

    • Optional Description column for headers, params, vars, and form fields with a toggle to show/hide.
    • Contextual "Add" button labels (e.g., Add Header, Add Variable, Add Param, Add Assertion).
    • Inline descriptions supported in import/export formats.
  • Improvements

    • Descriptions included in exports (environment & postman flows) and schema.
    • Multi-line editor refreshes improved for smoother description editing.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 13, 2026

Walkthrough

Adds an optional Description field end-to-end: UI toggleable Description columns with MultiLineEditor across tables, EditableTable add-row labeling, schema/types and converters updated to carry description, and BRU/ENV parser & serializer extended to extract and emit @description annotations.

Changes

Cohort / File(s) Summary
EditableTable
packages/bruno-app/src/components/EditableTable/index.js
New addRowLabel prop and handleAddRow to render a labeled "Add Row" control.
Headers & Vars UI (Collection / Folder / RequestPane)
packages/bruno-app/src/components/CollectionSettings/Headers/index.js, packages/bruno-app/src/components/FolderSettings/Headers/index.js, packages/bruno-app/src/components/RequestPane/RequestHeaders/index.js, packages/bruno-app/src/components/CollectionSettings/Vars/VarsTable/index.js, packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/index.js, packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js
Introduce local showDescriptionColumn state, add descriptionColumn using MultiLineEditor, include description in defaultRow, toggle UI, pass addRowLabel.
Param Tables (Query / Path / Form-Urlencoded / Multipart)
packages/bruno-app/src/components/RequestPane/QueryParams/index.js, packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js, packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js
Conditional Description column and editor, defaultRow description, toggle control, addRowLabel propagation; minor refactor (useCallback) in path param handler.
Environment Variables UI
packages/bruno-app/src/components/EnvironmentVariablesTable/index.js, packages/bruno-app/src/components/EnvironmentVariablesTable/StyledWrapper.js
Add togglable Description column, form integration (initialValues, validation, reset/restore), search filtering include description, styling for description column and layout tweaks.
MultiLineEditor
packages/bruno-app/src/components/MultiLineEditor/index.js
Schedule CodeMirror refresh via requestAnimationFrame after onChange and when value prop updates to ensure editor redraw.
Converters (Postman)
packages/bruno-converters/src/postman/bruno-to-postman.js, packages/bruno-converters/tests/postman/bruno-to-postman.spec.js
Emit description: '' on formUrlEncoded and multipart form items; update tests to expect new field.
Language Parsing (BRU → JSON)
packages/bruno-lang/v2/src/bruToJson.js, packages/bruno-lang/v2/src/collectionBruToJson.js, packages/bruno-lang/v2/src/envToJson.js, packages/bruno-lang/v2/src/common/semantic-utils.js
Add extractDescription(pair) to parse inline @description (triple- or double-quoted) from values, set description, trim value; add defensive checks.
Language Serialization (JSON → BRU/ENV)
packages/bruno-lang/v2/src/jsonToBru.js, packages/bruno-lang/v2/src/jsonToCollectionBru.js, packages/bruno-lang/v2/src/jsonToEnv.js
Add getDescriptionSuffix helpers and append description suffix when serializing items/vars so descriptions round-trip into BRU/ENV.
Schema / Types
packages/bruno-schema-types/src/collection/environment.ts, packages/bruno-schema/src/collections/index.js
Added optional `description?: string
Tests
packages/bruno-lang/v2/tests/bruToJson.spec.js, packages/bruno-lang/v2/tests/envToJson.spec.js, packages/bruno-lang/v2/tests/jsonToEnv.spec.js, packages/bruno-converters/tests/postman/bruno-to-postman.spec.js
Add/adjust tests to cover parsing/serialization of descriptions and updated converter outputs.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant UI as EditableTable/UI
    participant Editor as MultiLineEditor
    participant Form as App Form State
    participant Parser as BRU/ENV Parser
    participant Serializer as BRU/ENV Serializer

    User->>UI: Toggle "Description" column
    UI->>Editor: render per-row MultiLineEditor
    User->>Editor: Edit description text
    Editor->>UI: onChange(description)
    UI->>Form: update item.description
    Form->>Form: persist/update model

    User->>Serializer: Export BRU/ENV
    Serializer->>Form: read items (with description)
    Serializer->>Serializer: append `@description` suffix
    Serializer->>User: output BRU/ENV text

    User->>Parser: Import BRU/ENV
    Parser->>Parser: extractDescription(pair)
    Parser->>Form: populate item.description
    Form->>UI: render description column values
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • lohit-bruno
  • bijin-bruno
  • helloanoop

Poem

✨ A tiny column springs to light,
Where notes and whispers now alight.
Toggle, type, then watch it flow—
From editor to file it goes.
Descriptions dance in rows, aglow.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature: adding description columns to multiple components (Headers, VarsTable, and others) to enable multi-line descriptions.
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 docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

❤️ Share

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

Copy link
Contributor

@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: 4

Caution

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

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

31-37: ⚠️ Potential issue | 🟡 Minor

Column widths may become stale when columns are dynamically added/removed.

columnWidths is initialized once from the initial columns prop. When the description column is toggled on/off, the stored percentage widths (set after resize) for existing columns won't redistribute to accommodate the new column. This could cause layout overflow or underfill after a user has resized columns and then toggles the description column.

Consider resetting columnWidths when the columns array length changes:

Proposed fix
+ useEffect(() => {
+   const initialWidths = {};
+   columns.forEach((col) => {
+     initialWidths[col.key] = col.width || 'auto';
+   });
+   setColumnWidths(initialWidths);
+ }, [columns.length]);
packages/bruno-app/src/components/EnvironmentVariablesTable/index.js (1)

70-79: ⚠️ Potential issue | 🟡 Minor

Resize handler drops the description width key.

setColumnWidths on line 76-79 replaces the entire state object with only [columnKey] and [nextColumnKey] (hardcoded to 'value'). When the description column is visible, columnWidths.description becomes undefined after any resize, so the description column loses its explicit 25% width (line 452/524).

Proposed fix — spread existing widths
      setColumnWidths({
+       ...columnWidths,
        [columnKey]: `${startWidth + clampedDiff}px`,
        [nextColumnKey]: `${nextColumnStartWidth - clampedDiff}px`
      });

Note: you'll need to add columnWidths to the useCallback dependency array or use a functional updater (setColumnWidths((prev) => ({ ...prev, ... }))).

packages/bruno-converters/src/postman/bruno-to-postman.js (1)

231-243: ⚠️ Potential issue | 🟠 Major

generateHeaders must emit description field to support bidirectional conversion and match form converters pattern.

Looking at the codebase, formUrlEncoded (line 263) and multipartForm (line 276) both emit description: bodyItem.description || ''. Similarly, the postman-to-bruno converter reads header descriptions at line 468. The test suite also validates that header descriptions are preserved during conversion.

Since Bruno headers now carry a description property and Postman's v2.1.0 schema supports it, generateHeaders should emit the description field consistently with other body converters:

return {
  key: item.name || '',
  value: item.value || '',
  disabled: !item.enabled,
  type: 'default',
  description: item.description || ''
};
packages/bruno-lang/v2/src/bruToJson.js (1)

278-297: ⚠️ Potential issue | 🟡 Minor

Missing _.isString(pair.value) guard in mapPairListToKeyValPairsFile (line 283).

The multipart equivalent on line 268 has a defensive _.isString(pair.value) check before calling startsWith. This file path is missing the same guard, which could throw if pair.value is unexpectedly non-string.

🛡️ Add defensive check
-    if (pair.value.startsWith('@file(') && pair.value.endsWith(')')) {
+    if (_.isString(pair.value) && pair.value.startsWith('@file(') && pair.value.endsWith(')')) {
packages/bruno-lang/v2/src/jsonToBru.js (1)

186-205: ⚠️ Potential issue | 🟡 Minor

Add getDescriptionSuffix to metadata items for consistent description handling.

Metadata items (lines 191, 199) don't include the description suffix while headers, params, formUrlEncoded, and vars all do. The converters already handle descriptions on metadata, so jsonToBru should output them consistently.

🤖 Fix all issues with AI agents
In `@packages/bruno-app/src/components/CollectionSettings/Headers/index.js`:
- Around line 146-152: The Description toggle button omits the shared
"btn-action" class causing inconsistent styling; update the button rendered in
the component that uses showDescriptionColumn and setShowDescriptionColumn to
include "btn-action" alongside the existing "text-link select-none" className so
it matches other toggles like QueryParams and VarsTable.

In `@packages/bruno-app/src/components/EditableTable/index.js`:
- Around line 238-240: handleAddRow can be a no-op visually because
rowsWithEmpty already shows a trailing empty row when showAddRow is true; either
stop rendering that inline empty row when an explicit "Add" action is present or
provide focus/scroll feedback to the newly persisted row. Update the
rowsWithEmpty logic to skip appending a virtual empty row when addRowLabel (or
showAddRow) is truthy so the Add button creates the only visible empty row, or
keep rowsWithEmpty but augment handleAddRow to call onChange([...rows,
createEmptyRow()]) then programmatically focus/scroll the newly added row (use a
row ref or a focusRow(index) helper) so users see the new persisted row; adjust
any rendering hooks that compute rowsWithEmpty and the Add button handler
(handleAddRow, createEmptyRow, rows, onChange, showAddRow, addRowLabel)
accordingly.

In `@packages/bruno-app/src/components/FolderSettings/Headers/index.js`:
- Around line 152-158: The toggle button for description in the Headers
component is missing the "btn-action" utility class which causes inconsistent
styling; update the button that calls setShowDescriptionColumn and reads
showDescriptionColumn so its className becomes "btn-action text-link
select-none" (matching the other toggles) to restore consistent styling.

In `@packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js`:
- Around line 96-104: The Description toggle is placed above the table here but
should match the Headers components' placement below the table; move the button
rendering out of the current div above the table and render it alongside the
Bulk Edit controls below the params table, using the same state handlers
(showDescriptionColumn and setShowDescriptionColumn) and button className
("btn-action text-link select-none") so the toggle behavior and styling remain
unchanged; locate usages of showDescriptionColumn and setShowDescriptionColumn
in this component (FormUrlEncodedParams) and in
RequestHeaders/FolderSettings/Headers for reference to mirror layout and order.
🧹 Nitpick comments (11)
packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js (1)

39-57: Significant code duplication: descriptionColumn is nearly identical across 8+ components.

This exact pattern (key, name, placeholder, width, MultiLineEditor render) is repeated in FormUrlEncodedParams, MultipartFormParams, QueryParams, RequestHeaders, CollectionSettings/Headers, FolderSettings/Headers, and both VarsTable components. The only variation is whether onRun and item props are passed.

Consider extracting a shared factory, e.g.:

// utils/descriptionColumn.js
const createDescriptionColumn = ({ storedTheme, onSave, onChange, onRun, collection, item }) => ({ ... });

This would reduce the per-component boilerplate to a single call.

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

194-216: Minor inconsistency: description column defined inline instead of as a separate descriptionColumn const.

Other components in this PR extract the description column into a const descriptionColumn = { ... } for readability. This inline definition works but breaks the pattern.

packages/bruno-app/src/components/RequestPane/QueryParams/index.js (2)

39-53: pathParams in the dependency array defeats useCallback memoization.

pathParams is derived from a .filter() call on line 22, producing a new array reference every render. This means the callback is recreated on every render regardless, making useCallback a no-op here.

Consider using a stable reference (e.g., params or item) and moving the .find() inside the callback to look up the path param at call time:

Proposed fix
  const handlePathParamChange = useCallback(
    (rowUid, key, value) => {
+     const currentParams = item.draft ? get(item, 'draft.request.params') : get(item, 'request.params');
+     const pathParam = currentParams.find((p) => p.type === 'path' && p.uid === rowUid);
-     const pathParam = pathParams.find((p) => p.uid === rowUid);
      if (pathParam) {
        dispatch(
          updatePathParam({
            pathParam: { ...pathParam, [key]: value },
            itemUid: item.uid,
            collectionUid: collection.uid
          })
        );
      }
    },
-   [dispatch, pathParams, item.uid, collection.uid]
+   [dispatch, item, collection.uid]
  );

67-105: Significant duplication between descriptionColumnQuery and descriptionColumnPath.

These two objects are nearly identical. The only difference is that the path variant ignores the onChange prop and calls handlePathParamChange directly. This is fine within a single file, but noting that this same descriptionColumn pattern is also duplicated across 6+ other components (Headers, VarsTable, MultipartFormParams, FormUrlEncodedParams, etc.). Consider extracting a shared factory or helper.

packages/bruno-app/src/components/CollectionSettings/Vars/VarsTable/index.js (1)

91-99: Toggle button placement differs from other components.

Here the toggle is placed above the EditableTable (lines 91-99), while in CollectionSettings/Headers/index.js it's placed below (line 145-152). This creates an inconsistent UX within the same Collection Settings view. Consider aligning the placement.

packages/bruno-converters/tests/postman/bruno-to-postman.spec.js (1)

196-219: Consider adding a test for multipartForm items with description fallback.

The formUrlEncoded path now has coverage for the description field (lines 244-245), but the multipartForm path only tests with null (empty array). A parallel test with actual items would confirm the description: '' fallback for formdata entries as well.

🧪 Example test to add
+ it('should handle null or undefined items in multipart form data', () => {
+   const simpleCollection = {
+     items: [
+       {
+         name: 'Test Request',
+         type: 'http-request',
+         request: {
+           method: 'POST',
+           url: 'https://example.com',
+           body: {
+             mode: 'multipartForm',
+             multipartForm: [
+               { name: null, value: 'test-value', enabled: true },
+               { name: 'field', value: null, enabled: true }
+             ]
+           }
+         }
+       }
+     ]
+   };
+
+   const result = brunoToPostman(simpleCollection);
+   expect(result.item[0].request.body.formdata).toEqual([
+     { key: '', value: 'test-value', disabled: false, type: 'default', description: '' },
+     { key: 'field', value: '', disabled: false, type: 'default', description: '' }
+   ]);
+ });
packages/bruno-lang/v2/src/collectionBruToJson.js (1)

82-98: Duplicated extractDescription — consider importing from semantic-utils.js.

This exact function is copy-pasted in 4 files: semantic-utils.js, bruToJson.js, envToJson.js, and here. Since semantic-utils.js already exports it, importing from there would eliminate the maintenance burden of keeping all copies in sync.

♻️ Suggested change
+const { extractDescription } = require('./common/semantic-utils');
-const extractDescription = (pair) => {
-  if (!_.isString(pair.value)) {
-    return;
-  }
-  const tripleMatch = pair.value.match(/\s*@description\('''([\s\S]*?)'''\)\s*$/);
-  if (tripleMatch) {
-    pair.description = tripleMatch[1].trim();
-    pair.value = pair.value.slice(0, -tripleMatch[0].length).trim();
-    return;
-  }
-  const doubleMatch = pair.value.match(/\s*@description\("([^"]*)"\)\s*$/);
-  if (doubleMatch) {
-    pair.description = doubleMatch[1].trim();
-    pair.value = pair.value.slice(0, -doubleMatch[0].length).trim();
-    return;
-  }
-};
packages/bruno-lang/v2/src/envToJson.js (1)

49-65: Same extractDescription duplication as other files — reuse from semantic-utils.js.

See comment on collectionBruToJson.js. This is the same function duplicated again.

♻️ Suggested change
+const { extractDescription } = require('./common/semantic-utils');
-const extractDescription = (pair) => {
-  ...
-};
packages/bruno-lang/v2/src/jsonToBru.js (1)

9-16: getDescriptionSuffix is duplicated across jsonToBru.js, jsonToEnv.js, and jsonToCollectionBru.js.

Consider extracting this to utils.js alongside getValueString and getKeyString to keep a single source of truth.

packages/bruno-lang/v2/src/common/semantic-utils.js (1)

132-148: Redundant extractDescription call in mapPairListToKeyValPairsMultipart.

mapPairListToKeyValPairs (called on line 133) already invokes extractDescription on each pair (line 59). The second call on line 137 is a no-op since the @description(...) annotation has already been stripped from pair.value. Not harmful, but unnecessary work.

♻️ Remove redundant call
   return pairs.map((pair) => {
     pair.type = 'text';
-    extractDescription(pair);
     multipartExtractContentType(pair);
packages/bruno-lang/v2/src/jsonToCollectionBru.js (1)

8-15: Duplicated helper — consider extracting to utils.js.

getDescriptionSuffix is now identical in both jsonToBru.js and jsonToCollectionBru.js. Extracting it into utils.js alongside getValueString and getKeyString would keep a single source of truth.

#!/bin/bash
# Verify the duplication between the two files
echo "=== jsonToBru.js ==="
rg -n 'getDescriptionSuffix' --type=js -C2

Comment on lines +146 to +152
<button
type="button"
className="text-link select-none"
onClick={() => setShowDescriptionColumn((v) => !v)}
>
{showDescriptionColumn ? 'Hide Description' : 'Description'}
</button>
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 btn-action class on the Description toggle button.

Other components (e.g., QueryParams line 191, VarsTable line 94, EnvironmentVariablesTable line 565) include btn-action alongside text-link select-none. This button omits it, causing inconsistent styling.

          <button
            type="button"
-           className="text-link select-none"
+           className="btn-action text-link select-none"
            onClick={() => setShowDescriptionColumn((v) => !v)}
          >
📝 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
<button
type="button"
className="text-link select-none"
onClick={() => setShowDescriptionColumn((v) => !v)}
>
{showDescriptionColumn ? 'Hide Description' : 'Description'}
</button>
<button
type="button"
className="btn-action text-link select-none"
onClick={() => setShowDescriptionColumn((v) => !v)}
>
{showDescriptionColumn ? 'Hide Description' : 'Description'}
</button>
🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/CollectionSettings/Headers/index.js` around
lines 146 - 152, The Description toggle button omits the shared "btn-action"
class causing inconsistent styling; update the button rendered in the component
that uses showDescriptionColumn and setShowDescriptionColumn to include
"btn-action" alongside the existing "text-link select-none" className so it
matches other toggles like QueryParams and VarsTable.

Comment on lines +238 to +240
const handleAddRow = useCallback(() => {
onChange([...rows, createEmptyRow()]);
}, [rows, onChange, createEmptyRow]);
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

handleAddRow may produce a no-op visual effect when showAddRow is true.

When showAddRow is true, rowsWithEmpty already renders a trailing empty row (not persisted in rows). Clicking the "Add" button calls handleAddRow, which persists a new empty row into rows — but the visual result is the same empty row the user already saw. This could confuse users expecting a visible change.

Consider either hiding the inline empty row when addRowLabel is provided, or scrolling/focusing the newly added row to give feedback.

🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/EditableTable/index.js` around lines 238 -
240, handleAddRow can be a no-op visually because rowsWithEmpty already shows a
trailing empty row when showAddRow is true; either stop rendering that inline
empty row when an explicit "Add" action is present or provide focus/scroll
feedback to the newly persisted row. Update the rowsWithEmpty logic to skip
appending a virtual empty row when addRowLabel (or showAddRow) is truthy so the
Add button creates the only visible empty row, or keep rowsWithEmpty but augment
handleAddRow to call onChange([...rows, createEmptyRow()]) then programmatically
focus/scroll the newly added row (use a row ref or a focusRow(index) helper) so
users see the new persisted row; adjust any rendering hooks that compute
rowsWithEmpty and the Add button handler (handleAddRow, createEmptyRow, rows,
onChange, showAddRow, addRowLabel) accordingly.

Comment on lines +152 to +158
<button
type="button"
className="text-link select-none"
onClick={() => setShowDescriptionColumn((v) => !v)}
>
{showDescriptionColumn ? 'Hide Description' : 'Description'}
</button>
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 btn-action class on the description toggle button.

All other description toggle buttons in this PR use className="btn-action text-link select-none", but this one omits btn-action. This will cause inconsistent button styling.

Proposed fix
        <button
          type="button"
-          className="text-link select-none"
+          className="btn-action text-link select-none"
          onClick={() => setShowDescriptionColumn((v) => !v)}
        >
📝 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
<button
type="button"
className="text-link select-none"
onClick={() => setShowDescriptionColumn((v) => !v)}
>
{showDescriptionColumn ? 'Hide Description' : 'Description'}
</button>
<button
type="button"
className="btn-action text-link select-none"
onClick={() => setShowDescriptionColumn((v) => !v)}
>
{showDescriptionColumn ? 'Hide Description' : 'Description'}
</button>
🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/FolderSettings/Headers/index.js` around
lines 152 - 158, The toggle button for description in the Headers component is
missing the "btn-action" utility class which causes inconsistent styling; update
the button that calls setShowDescriptionColumn and reads showDescriptionColumn
so its className becomes "btn-action text-link select-none" (matching the other
toggles) to restore consistent styling.

Comment on lines +96 to +104
<div className="flex justify-end mt-2 mb-2">
<button
type="button"
className="btn-action text-link select-none"
onClick={() => setShowDescriptionColumn((v) => !v)}
>
{showDescriptionColumn ? 'Hide Description' : 'Description'}
</button>
</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

Toggle button placement is inconsistent with Headers components.

Here the description toggle is rendered above the table, while in RequestHeaders and FolderSettings/Headers it's rendered below the table alongside "Bulk Edit". Consider a consistent placement across all components for a uniform UX.

🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js`
around lines 96 - 104, The Description toggle is placed above the table here but
should match the Headers components' placement below the table; move the button
rendering out of the current div above the table and render it alongside the
Bulk Edit controls below the params table, using the same state handlers
(showDescriptionColumn and setShowDescriptionColumn) and button className
("btn-action text-link select-none") so the toggle behavior and styling remain
unchanged; locate usages of showDescriptionColumn and setShowDescriptionColumn
in this component (FormUrlEncodedParams) and in
RequestHeaders/FolderSettings/Headers for reference to mirror layout and order.

@Pragadesh-45 Pragadesh-45 marked this pull request as ready for review February 13, 2026 15:28
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/EnvironmentVariablesTable/index.js (1)

70-79: ⚠️ Potential issue | 🟠 Major

Bug: Resize drops the description width from state.

setColumnWidths on line 76 replaces the entire state object with only the two columns being resized (name and value). The description key initialized on line 51 is lost after any resize operation.

After a resize, columnWidths.description becomes undefined, and the description column at line 526 (style={{ width: columnWidths.description }}) loses its width styling.

🐛 Proposed fix — spread existing widths
      setColumnWidths({
+       ...columnWidths,
        [columnKey]: `${startWidth + clampedDiff}px`,
        [nextColumnKey]: `${nextColumnStartWidth - clampedDiff}px`
      });

Note: Since handleResizeStart has [] as its dependency array, you'd also need to either add columnWidths to the deps or use the functional updater form:

-     setColumnWidths({
-       [columnKey]: `${startWidth + clampedDiff}px`,
-       [nextColumnKey]: `${nextColumnStartWidth - clampedDiff}px`
-     });
+     setColumnWidths((prev) => ({
+       ...prev,
+       [columnKey]: `${startWidth + clampedDiff}px`,
+       [nextColumnKey]: `${nextColumnStartWidth - clampedDiff}px`
+     }));
🤖 Fix all issues with AI agents
In `@packages/bruno-app/src/components/EnvironmentVariablesTable/index.js`:
- Around line 563-580: The Description toggle button uses the wrong CSS classes
causing inconsistent styling; update the button for toggling description
visibility (the element that calls setShowDescriptionColumn) to use the same
classes as other toggles (e.g., "btn-action text-link select-none") instead of
"submit description-toggle ml-2" so it visually matches
MultipartFormParams/QueryParams/VarsTable; ensure the Save and Reset buttons
remain unchanged and keep the onClick handler that toggles showDescriptionColumn
intact.

In `@packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js`:
- Around line 229-237: The Description toggle button is rendered above the
EditableTable here, causing inconsistent UX; move the toggle block so it appears
after the EditableTable component. Locate the JSX that renders the button using
showDescriptionColumn and onClick={() => setShowDescriptionColumn((v) => !v)}
and cut/paste that <div> so it sits immediately below the <EditableTable />
render (matching placement used in CollectionSettings/Headers and VarsTable),
preserving the same className and handlers.

In `@packages/bruno-lang/v2/src/common/semantic-utils.js`:
- Around line 7-25: extractDescription currently captures description text but
doesn't reverse the escaping done by getDescriptionSuffix, and its single-line
regex fails to handle escaped quotes; update extractDescription to (1) use a
safer double-line regex for the single-line case (change
/\s*@description\("([^"]*)"\)\s*$/ to something like
/\s*@description\("((?:[^"\\]|\\.)*)"\)\s*$/) so escaped quotes are preserved in
the capture, and (2) after obtaining tripleMatch[1] or doubleMatch[1] unescape
sequences produced by getDescriptionSuffix by first replacing escaped
backslashes (\\ -> \) and then escaped quotes (\" -> ") before assigning to
pair.description, and then trim and remove the suffix from pair.value as before;
apply this to both tripleMatch and doubleMatch handling so round-trip saves do
not double-escape.
🧹 Nitpick comments (2)
packages/bruno-app/src/components/EnvironmentVariablesTable/StyledWrapper.js (1)

183-189: Inconsistent !important usage between .secret-column and .actions-column.

.secret-column uses !important to override the nth-child width rules, but .actions-column does not. If both classes are applied to <td> elements that also match an nth-child selector with a width, the actions-column width may be silently overridden. Either both should use !important or neither should (relying on specificity instead).

♻️ Suggested consistency fix
   .secret-column {
     width: 8% !important;
   }

   .actions-column {
-    width: 5%;
+    width: 5% !important;
   }
packages/bruno-lang/v2/src/common/semantic-utils.js (1)

132-148: extractDescription is called twice on multipart pairs — redundant.

mapPairListToKeyValPairs (invoked at line 133) already calls extractDescription at line 59. The second call at line 137 is always a no-op since the annotation has already been stripped. Remove line 137.

Proposed fix
   return pairs.map((pair) => {
     pair.type = 'text';
-    extractDescription(pair);
     multipartExtractContentType(pair);

Comment on lines +229 to +237
<div className="flex justify-end mt-2 mb-2">
<button
type="button"
className="btn-action text-link select-none"
onClick={() => setShowDescriptionColumn((v) => !v)}
>
{showDescriptionColumn ? 'Hide Description' : 'Description'}
</button>
</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

Description toggle placed above the table here, but below in other components.

In CollectionSettings/Headers, VarsTable, etc., the toggle button appears below the table. Here it's rendered above the EditableTable. This creates an inconsistent UX across the app.

Consider moving it below the table (after EditableTable) for consistency with the other editors in this PR.

🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js`
around lines 229 - 237, The Description toggle button is rendered above the
EditableTable here, causing inconsistent UX; move the toggle block so it appears
after the EditableTable component. Locate the JSX that renders the button using
showDescriptionColumn and onClick={() => setShowDescriptionColumn((v) => !v)}
and cut/paste that <div> so it sits immediately below the <EditableTable />
render (matching placement used in CollectionSettings/Headers and VarsTable),
preserving the same className and handlers.

Comment on lines +7 to +25
const extractDescription = (pair) => {
if (!_.isString(pair.value)) {
return;
}
// Multiline: @description('''...''')
const tripleMatch = pair.value.match(/\s*@description\('''([\s\S]*?)'''\)\s*$/);
if (tripleMatch) {
pair.description = tripleMatch[1].trim();
pair.value = pair.value.slice(0, -tripleMatch[0].length).trim();
return;
}
// Single-line: @description("...")
const doubleMatch = pair.value.match(/\s*@description\("([^"]*)"\)\s*$/);
if (doubleMatch) {
pair.description = doubleMatch[1].trim();
pair.value = pair.value.slice(0, -doubleMatch[0].length).trim();
return;
}
};
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

Round-trip escaping mismatch: getDescriptionSuffix escapes but extractDescription never unescapes.

getDescriptionSuffix applies replace(/\\/g, '\\\\') and replace(/"/g, '\\"') when serializing. But extractDescription returns the captured group verbatim without reversing these escapes. After one round-trip, every \ in a description becomes \\, and the corruption doubles on each subsequent save.

Add unescaping after extraction:

Proposed fix
   const tripleMatch = pair.value.match(/\s*@description\('''([\s\S]*?)'''\)\s*$/);
   if (tripleMatch) {
-    pair.description = tripleMatch[1].trim();
+    pair.description = tripleMatch[1].trim().replace(/\\\\/g, '\\');
     pair.value = pair.value.slice(0, -tripleMatch[0].length).trim();
     return;
   }
   // Single-line: `@description`("...")
-  const doubleMatch = pair.value.match(/\s*@description\("([^"]*)"\)\s*$/);
+  const doubleMatch = pair.value.match(/\s*@description\("((?:[^"\\]|\\.)*)"\)\s*$/);
   if (doubleMatch) {
-    pair.description = doubleMatch[1].trim();
+    pair.description = doubleMatch[1].trim().replace(/\\"/g, '"').replace(/\\\\/g, '\\');
     pair.value = pair.value.slice(0, -doubleMatch[0].length).trim();
     return;
   }

The double-quote regex is also changed from [^"]* to (?:[^"\\]|\\.)* to correctly handle escaped quotes (e.g., descriptions containing both ''' and ").

📝 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 extractDescription = (pair) => {
if (!_.isString(pair.value)) {
return;
}
// Multiline: @description('''...''')
const tripleMatch = pair.value.match(/\s*@description\('''([\s\S]*?)'''\)\s*$/);
if (tripleMatch) {
pair.description = tripleMatch[1].trim();
pair.value = pair.value.slice(0, -tripleMatch[0].length).trim();
return;
}
// Single-line: @description("...")
const doubleMatch = pair.value.match(/\s*@description\("([^"]*)"\)\s*$/);
if (doubleMatch) {
pair.description = doubleMatch[1].trim();
pair.value = pair.value.slice(0, -doubleMatch[0].length).trim();
return;
}
};
const extractDescription = (pair) => {
if (!_.isString(pair.value)) {
return;
}
// Multiline: `@description`('''...''')
const tripleMatch = pair.value.match(/\s*@description\('''([\s\S]*?)'''\)\s*$/);
if (tripleMatch) {
pair.description = tripleMatch[1].trim().replace(/\\\\/g, '\\');
pair.value = pair.value.slice(0, -tripleMatch[0].length).trim();
return;
}
// Single-line: `@description`("...")
const doubleMatch = pair.value.match(/\s*@description\("((?:[^"\\]|\\.)*)"\)\s*$/);
if (doubleMatch) {
pair.description = doubleMatch[1].trim().replace(/\\"/g, '"').replace(/\\\\/g, '\\');
pair.value = pair.value.slice(0, -doubleMatch[0].length).trim();
return;
}
};
🤖 Prompt for AI Agents
In `@packages/bruno-lang/v2/src/common/semantic-utils.js` around lines 7 - 25,
extractDescription currently captures description text but doesn't reverse the
escaping done by getDescriptionSuffix, and its single-line regex fails to handle
escaped quotes; update extractDescription to (1) use a safer double-line regex
for the single-line case (change /\s*@description\("([^"]*)"\)\s*$/ to something
like /\s*@description\("((?:[^"\\]|\\.)*)"\)\s*$/) so escaped quotes are
preserved in the capture, and (2) after obtaining tripleMatch[1] or
doubleMatch[1] unescape sequences produced by getDescriptionSuffix by first
replacing escaped backslashes (\\ -> \) and then escaped quotes (\" -> ") before
assigning to pair.description, and then trim and remove the suffix from
pair.value as before; apply this to both tripleMatch and doubleMatch handling so
round-trip saves do not double-escape.

…tions

- Introduced a description column in Headers, VarsTable, and other components to allow multi-line descriptions.
- Added toggle buttons to show/hide the description column in relevant tables.
- Updated EditableTable component to support dynamic row addition with customizable labels.
- Enhanced UI for better user experience with consistent button styles and spacing adjustments.
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 (3)
packages/bruno-app/src/components/EnvironmentVariablesTable/StyledWrapper.js (1)

66-71: ⚠️ Potential issue | 🟡 Minor

Double borders on header cells after adding border-left.

Line 70 adds border-left to every thead td, but line 69 already applies border-right. Adjacent cells will now render a 2px seam (right border of cell N + left border of cell N+1). If this is intentional for a visual effect, please disregard — otherwise, consider using only border-right (or only border-left) and handling the edge cell separately.

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

In `@packages/bruno-app/src/components/EnvironmentVariablesTable/StyledWrapper.js`
around lines 66 - 71, The header cell CSS selector (thead td) is creating double
seams because both border-right and border-left are applied; remove border-left
from the thead td rule (or keep only border-left and remove border-right) and
instead add a single-border rule for the table edge using a pseudo/edge selector
(e.g., thead td:first-child or thead td:last-child) to ensure only one border
between adjacent header cells; update the StyledWrapper.js thead td rule
accordingly and adjust any tests/styles that expect the previous double-border
behavior.
packages/bruno-app/src/components/EnvironmentVariablesTable/index.js (2)

63-66: ⚠️ Potential issue | 🟡 Minor

nextColumnKey is hardcoded to 'value' — resize logic ignores description column.

When showDescriptionColumn is true, there are more columns in play, but nextColumnKey is always 'value'. If the user resizes the name column, the value column shrinks as expected. But the description column's resize handle (if any) also targets 'value', which is incorrect. Consider deriving nextColumnKey from the actual next sibling column's data attribute or from the column order.

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

In `@packages/bruno-app/src/components/EnvironmentVariablesTable/index.js` around
lines 63 - 66, The resize handler currently hardcodes nextColumnKey = 'value',
causing wrong column targeting when showDescriptionColumn is true; update the
logic in the resize start (where startX, startWidth, nextCell are set) to derive
nextColumnKey from the actual next sibling column element or from the columns
order (e.g., read a data-column-key attribute on nextCell or use the columns
array to find the column after currentCell) so the resize updates the correct
column (name -> value or name -> description depending on showDescriptionColumn)
and adjust any code paths that reference nextColumnKey accordingly.

70-79: ⚠️ Potential issue | 🟠 Major

Resize handler drops description width — column collapses after any resize.

setColumnWidths on lines 76-79 sets only name and value keys, overwriting the entire state object. The description key from the initial state (line 51) is lost. After any column resize, the description column will lose its 25% width and render with no explicit width style.

Spread existing state or include the description key:

Proposed fix
-      setColumnWidths({
-        [columnKey]: `${startWidth + clampedDiff}px`,
-        [nextColumnKey]: `${nextColumnStartWidth - clampedDiff}px`
-      });
+      setColumnWidths((prev) => ({
+        ...prev,
+        [columnKey]: `${startWidth + clampedDiff}px`,
+        [nextColumnKey]: `${nextColumnStartWidth - clampedDiff}px`
+      }));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/EnvironmentVariablesTable/index.js` around
lines 70 - 79, handleMouseMove currently calls setColumnWidths with an object
containing only [columnKey] and [nextColumnKey], which overwrites the entire
widths state and drops the existing description width; change the update to
merge with the existing state (e.g., use functional setState and spread prev:
setColumnWidths(prev => ({ ...prev, [columnKey]: ..., [nextColumnKey]: ... })))
so description (and any other keys) are preserved; locate handleMouseMove and
the setColumnWidths call, and use the functional updater pattern referencing
columnKey, nextColumnKey, startWidth, nextColumnStartWidth and MIN_COLUMN_WIDTH
to apply the clamped widths without replacing other keys.
🧹 Nitpick comments (14)
packages/bruno-app/src/components/RequestPane/QueryParams/index.js (4)

67-105: Near-duplicate column definitions for description.

descriptionColumnQuery and descriptionColumnPath share the same key, name, placeholder, width, and nearly identical render bodies — the only real difference is how onChange is handled. Consider extracting a shared factory:

♻️ Optional: shared factory
-  const descriptionColumnQuery = {
-    key: 'description',
-    name: 'Description',
-    placeholder: 'Description',
-    width: '25%',
-    render: ({ value, onChange }) => (
-      <MultiLineEditor
-        value={value || ''}
-        theme={storedTheme}
-        onSave={onSave}
-        onChange={onChange}
-        allowNewlines={true}
-        onRun={handleRun}
-        collection={collection}
-        item={item}
-        placeholder={!value ? 'Description' : ''}
-      />
-    )
-  };
-
-  const descriptionColumnPath = {
-    key: 'description',
-    name: 'Description',
-    placeholder: 'Description',
-    width: '25%',
-    render: ({ row, value, onChange }) => (
-      <MultiLineEditor
-        value={value || ''}
-        theme={storedTheme}
-        onSave={onSave}
-        onChange={(newValue) => handlePathParamChange(row.uid, 'description', newValue)}
-        allowNewlines={true}
-        onRun={handleRun}
-        collection={collection}
-        item={item}
-        placeholder={!value ? 'Description' : ''}
-      />
-    )
-  };
+  const makeDescriptionColumn = (onChangeFn) => ({
+    key: 'description',
+    name: 'Description',
+    placeholder: 'Description',
+    width: '25%',
+    render: ({ row, value, onChange }) => (
+      <MultiLineEditor
+        value={value || ''}
+        theme={storedTheme}
+        onSave={onSave}
+        onChange={onChangeFn ? (newValue) => onChangeFn(row.uid, 'description', newValue) : onChange}
+        allowNewlines={true}
+        onRun={handleRun}
+        collection={collection}
+        item={item}
+        placeholder={!value ? 'Description' : ''}
+      />
+    )
+  });
+
+  const descriptionColumnQuery = makeDescriptionColumn(null);
+  const descriptionColumnPath = makeDescriptionColumn(handlePathParamChange);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/QueryParams/index.js` around
lines 67 - 105, descriptionColumnQuery and descriptionColumnPath are almost
identical and should be merged via a factory to avoid duplication and accidental
drift; create a helper like createDescriptionColumn({ onChangeOverride }) or
createDescriptionColumn({ isPath }) that returns the column object used for both
cases, keeping shared properties (key, name, placeholder, width, theme, onSave,
allowNewlines, onRun, collection, item, placeholder logic) and injecting the
differing onChange behavior (either the passed onChange or a wrapper that calls
handlePathParamChange(row.uid, 'description', newValue)); update the uses of
descriptionColumnQuery and descriptionColumnPath to call this factory and remove
the duplicated definitions.

187-196: Toggle button label could be clearer when collapsed.

When the column is hidden the label reads just "Description", which doesn't immediately signal an action. Consider "Show Description" / "Hide Description" for symmetry so users know it's a toggle.

♻️ Suggested tweak
-            {showDescriptionColumn ? 'Hide Description' : 'Description'}
+            {showDescriptionColumn ? 'Hide Description' : 'Show Description'}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/QueryParams/index.js` around
lines 187 - 196, The toggle button currently shows "Description" when the column
is hidden; update the button label logic in the QueryParams component to be
explicit by using the showDescriptionColumn state so it reads "Show Description"
when showDescriptionColumn is false and "Hide Description" when true; locate the
JSX that renders the button (the onClick uses setShowDescriptionColumn) and
replace the existing ternary/string with a clear "Show Description" / "Hide
Description" pair so the action is unambiguous to users.

92-97: onChange prop silently ignored — worth a brief inline comment.

descriptionColumnPath.render receives onChange from EditableTable but deliberately bypasses it in favor of handlePathParamChange. This is correct (path params use a per-field dispatch), but a one-liner comment would save future readers a head-scratch.

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

In `@packages/bruno-app/src/components/RequestPane/QueryParams/index.js` around
lines 92 - 97, descriptionColumnPath.render currently receives an onChange prop
from EditableTable but intentionally ignores it by calling handlePathParamChange
instead; add a concise inline comment next to the MultiLineEditor onChange (or
above descriptionColumnPath.render) stating that the prop is deliberately
bypassed because path params use a per-field dispatch via
handlePathParamChange(row.uid, 'description', ...) so future readers won't be
confused. Keep the comment to one line and reference the use of
handlePathParamChange and row.uid for clarity.

39-53: useCallback is effectively a no-op here.

pathParams (line 52) is derived via .filter() on line 22, producing a new array reference every render. This means the dependency array always changes and the callback is never truly memoized.

Two options to actually get memoization benefit:

  1. Move the pathParams.find() lookup out of the callback and pass uid directly to the dispatch, performing the lookup at the reducer level.
  2. Use useMemo to stabilize pathParams first, then reference it in this callback's deps.

Not a bug — just dead memoization.

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

In `@packages/bruno-app/src/components/RequestPane/QueryParams/index.js` around
lines 39 - 53, handlePathParamChange is never truly memoized because pathParams
is recreated each render; either move the find() lookup into the reducer action
(so handlePathParamChange sends the rowUid and key/value to dispatch and let
updatePathParam resolve the pathParam by uid), or stabilize pathParams with
useMemo before using it in the useCallback dependency array. Specifically,
either change handlePathParamChange to dispatch({ type: updatePathParam,
payload: { uid: rowUid, key, value }, itemUid: item.uid, collectionUid:
collection.uid }) and resolve the item in the reducer, or wrap the pathParams
filter in useMemo (e.g. const pathParams = useMemo(() =>
originalPathParams.filter(...), [originalPathParams]) ) and keep the existing
handlePathParamChange that references pathParams, dispatch, item.uid,
collection.uid.
packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/index.js (1)

38-55: Optional: memoize descriptionColumn with useMemo.

descriptionColumn (and its render closure) is recreated on every render. If EditableTable uses column reference equality for bailouts, this will defeat them. While onSave itself is also not memoized (pre-existing), wrapping descriptionColumn in useMemo would at least scope the instability.

♻️ Proposed refactor
-  const descriptionColumn = {
-    key: 'description',
-    name: 'Description',
-    placeholder: 'Description',
-    width: '25%',
-    render: ({ value, onChange }) => (
-      <MultiLineEditor
-        value={value || ''}
-        theme={storedTheme}
-        onSave={onSave}
-        onChange={onChange}
-        allowNewlines={true}
-        collection={collection}
-        item={folder}
-        placeholder={!value ? 'Description' : ''}
-      />
-    )
-  };
+  const descriptionColumn = useMemo(() => ({
+    key: 'description',
+    name: 'Description',
+    placeholder: 'Description',
+    width: '25%',
+    render: ({ value, onChange }) => (
+      <MultiLineEditor
+        value={value || ''}
+        theme={storedTheme}
+        onSave={onSave}
+        onChange={onChange}
+        allowNewlines={true}
+        collection={collection}
+        item={folder}
+        placeholder={!value ? 'Description' : ''}
+      />
+    )
+  }), [storedTheme, onSave, collection, folder]);

Note: useMemo is already imported from React in this file (via useCallback). You'd need to add it to the import on line 1.

Based on learnings: "Memoize only when necessary (useMemo/useCallback), and prefer moving logic into hooks first (SHOULD)."

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

In `@packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/index.js`
around lines 38 - 55, The descriptionColumn object (including its render closure
that returns MultiLineEditor) is recreated on every render which can break
EditableTable’s column reference equality optimizations; wrap the
descriptionColumn creation in React's useMemo (and add useMemo to the existing
React imports) so the column reference is stable unless its dependencies change
(e.g., storedTheme, onSave, collection, folder); ensure the memo dependency
array includes storedTheme, onSave, collection and folder (and any values used
inside render) so the column is recomputed only when needed.
packages/bruno-app/src/components/CollectionSettings/Vars/VarsTable/index.js (1)

33-49: descriptionColumn is always constructed even when hidden.

The object and its render closure are recreated on every render regardless of showDescriptionColumn. Since columns is also not memoized (existing pattern), this is low-priority — but wrapping in useMemo with [storedTheme, onSave, collection] deps would align with the project's "memoize when beneficial" guideline.

♻️ Proposed memoization
-  const descriptionColumn = {
+  const descriptionColumn = useMemo(() => ({
     key: 'description',
     name: 'Description',
     placeholder: 'Description',
     width: '25%',
     render: ({ value, onChange }) => (
       <MultiLineEditor
         value={value || ''}
         theme={storedTheme}
         onSave={onSave}
         onChange={onChange}
         allowNewlines={true}
         collection={collection}
         placeholder={!value ? 'Description' : ''}
       />
     )
-  };
+  }), [storedTheme, onSave, collection]);

Based on learnings: "Memoize only when necessary (useMemo/useCallback), and prefer moving logic into hooks first (SHOULD)."

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

In `@packages/bruno-app/src/components/CollectionSettings/Vars/VarsTable/index.js`
around lines 33 - 49, descriptionColumn is rebuilt on every render even when
hidden; wrap its creation in useMemo and only return the object when
showDescriptionColumn is true. Use React.useMemo(() => { ... }, [storedTheme,
onSave, collection]) so the render closure passed to MultiLineEditor captures
stable storedTheme, onSave and collection, and skip creating descriptionColumn
when showDescriptionColumn is false (return null/undefined) so columns assembly
can conditionally include it without recreating the function each render.
packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js (1)

48-66: Same descriptionColumn memoization opportunity as the CollectionSettings variant.

Same rationale applies here; deps would be [storedTheme, onSave, handleRun, collection, item].

♻️ Proposed memoization
-  const descriptionColumn = {
+  const descriptionColumn = useMemo(() => ({
     key: 'description',
     name: 'Description',
     placeholder: 'Description',
     width: '25%',
     render: ({ value, onChange }) => (
       <MultiLineEditor
         value={value || ''}
         theme={storedTheme}
         onSave={onSave}
         onChange={onChange}
         allowNewlines={true}
         onRun={handleRun}
         collection={collection}
         item={item}
         placeholder={!value ? 'Description' : ''}
       />
     )
-  };
+  }), [storedTheme, onSave, handleRun, collection, item]);

Note: useMemo is already imported via useCallback sibling — just add it to the import destructuring.

Based on learnings: "Memoize only when necessary (useMemo/useCallback), and prefer moving logic into hooks first (SHOULD)."

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

In `@packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js` around
lines 48 - 66, descriptionColumn is recreated on every render; wrap its
definition in useMemo and include the dependencies [storedTheme, onSave,
handleRun, collection, item] so the rendered MultiLineEditor props are stable,
and add useMemo to the import list; keep the same shape and return value but
memoize the const descriptionColumn = useMemo(() => ({ key: 'description', name:
'Description', placeholder: 'Description', width: '25%', render: ({ value,
onChange }) => (<MultiLineEditor ... />) }), [storedTheme, onSave, handleRun,
collection, item]).
packages/bruno-lang/v2/src/jsonToEnv.js (1)

4-11: getDescriptionSuffix is duplicated verbatim from jsonToBru.js — move it to utils.js.

packages/bruno-lang/v2/src/jsonToBru.js lines 8-15 already define an identical function (same logic, same escaping, only parameter name differs). Keeping two copies will diverge silently when either needs a bug fix.

Move getDescriptionSuffix into utils.js and export it, then import it in both jsonToBru.js and jsonToEnv.js.

♻️ Proposed refactor

In utils.js, add and export the shared helper:

+/**
+ * Returns a formatted `@description`(...) suffix for serialization.
+ * Uses triple-quote syntax unless the description itself contains triple quotes,
+ * in which case it falls back to double-quote syntax.
+ * `@param` {{ description?: string }} item
+ * `@returns` {string}
+ */
+const getDescriptionSuffix = (item) => {
+  const desc = item && item.description && String(item.description).trim();
+  if (!desc) return '';
+  if (desc.includes('\'\'\'')) {
+    return ' `@description`("' + desc.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '")';
+  }
+  return ' `@description`(\'\'\'' + desc.replace(/\\/g, '\\\\') + '\'\'\')';
+};
+
+module.exports = { indentString, getValueString, getDescriptionSuffix };

In jsonToEnv.js:

-const { getValueString, indentString } = require('./utils');
+const { getValueString, indentString, getDescriptionSuffix } = require('./utils');

-const getDescriptionSuffix = (variable) => {
-  const desc = variable && variable.description && String(variable.description).trim();
-  if (!desc) return '';
-  if (desc.includes('\'\'\'')) {
-    return ' `@description`("' + desc.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '")';
-  }
-  return ' `@description`(\'\'\'' + desc.replace(/\\/g, '\\\\') + '\'\'\')';
-};

Apply the same import change in jsonToBru.js.

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

In `@packages/bruno-lang/v2/src/jsonToEnv.js` around lines 4 - 11, The
getDescriptionSuffix function is duplicated in jsonToEnv.js and jsonToBru.js;
extract it into packages/bruno-lang/v2/src/utils.js as an exported helper
(preserve its current logic and escaping behavior and keep the function name
getDescriptionSuffix), then remove the duplicate definitions from jsonToEnv.js
and jsonToBru.js and replace them with imports that import {
getDescriptionSuffix } from './utils'; so both modules use the single shared
implementation.
packages/bruno-app/src/components/EnvironmentVariablesTable/StyledWrapper.js (1)

183-189: secret-column uses !important but actions-column does not.

Inconsistent specificity approach. If !important is needed on .secret-column to override the nth-child width, the same concern applies to .actions-column. Either both should use !important or neither should (by increasing selector specificity instead).

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

In `@packages/bruno-app/src/components/EnvironmentVariablesTable/StyledWrapper.js`
around lines 183 - 189, The .secret-column currently uses !important while
.actions-column does not, causing inconsistent specificity; update
StyledWrapper.js so both column rules use consistent specificity — either remove
the !important from .secret-column and increase selector specificity for both
(.secret-column and .actions-column) to override the nth-child width, or add
!important to .actions-column as well; locate and change the styles for
.secret-column and .actions-column so they match in specificity and reliably
override the nth-child selector.
packages/bruno-app/src/components/EnvironmentVariablesTable/index.js (1)

427-432: Description search filter — minor redundancy.

variable.description && typeof variable.description === 'string' — the && with a truthy check is redundant if you're already checking typeof ... === 'string' (a non-empty string is truthy, but typeof '' === 'string' is also true, so the first clause excludes '' unnecessarily). An empty description won't match any real query anyway, so it's functionally fine, just slightly misleading.

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

In `@packages/bruno-app/src/components/EnvironmentVariablesTable/index.js` around
lines 427 - 432, Summary: descriptionMatch uses a redundant truthy check before
typeof; remove it. Update the filter expression in the EnvironmentVariablesTable
component to compute descriptionMatch by only checking typeof
variable.description === 'string' (and then calling
.toLowerCase().includes(query) if true), so empty strings aren't specially
excluded and the logic is clearer; adjust the variable named descriptionMatch
and the return that uses nameMatch, valueMatch, descriptionMatch accordingly.
packages/bruno-lang/v2/src/common/semantic-utils.js (1)

133-147: Redundant double extractDescription call in mapPairListToKeyValPairsMultipart.

mapPairListToKeyValPairs (Line 133) already calls extractDescription on each pair when parseEnabled = true (the default). The explicit call at Line 137 is always a no-op for that path — the annotation is already stripped before reaching it.

The call is only meaningful when parseEnabled = false, which skips the extraction in the inner function. Consider calling it conditionally:

♻️ Suggested fix
  return pairs.map((pair) => {
    pair.type = 'text';
-   extractDescription(pair);
+   if (!parseEnabled) extractDescription(pair);
    multipartExtractContentType(pair);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-lang/v2/src/common/semantic-utils.js` around lines 133 - 147,
In mapPairListToKeyValPairsMultipart, remove the redundant unconditional call to
extractDescription for each pair (currently after mapping pairs from
mapPairListToKeyValPairs) and instead only call extractDescription when
parseEnabled is false; reference mapPairListToKeyValPairs (which already
extracts descriptions when parseEnabled === true), the local parseEnabled flag,
and the extractDescription function to implement the conditional so descriptions
are only stripped once.
packages/bruno-lang/v2/src/jsonToCollectionBru.js (1)

8-15: getDescriptionSuffix is duplicated across three files.

Identical implementations exist in jsonToBru.js (Lines 9–16) and jsonToEnv.js (Lines 3–10). Extract it to a shared utility (e.g., common/semantic-utils.js or utils.js) and import from there.

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

In `@packages/bruno-lang/v2/src/jsonToCollectionBru.js` around lines 8 - 15,
getDescriptionSuffix is duplicated across jsonToCollectionBru.js, jsonToBru.js,
and jsonToEnv.js; extract this function into a shared utility module (e.g.,
common/semantic-utils or utils) and replace the copies with imports. Create a
new exported function getDescriptionSuffix in the shared module using the same
implementation (preserve string escaping logic and return values), update
jsonToCollectionBru.js, jsonToBru.js, and jsonToEnv.js to import
getDescriptionSuffix and remove their local implementations, and run tests/lint
to ensure no references remain to the old local functions.
packages/bruno-lang/v2/src/collectionBruToJson.js (1)

82-98: extractDescription duplicated instead of using the shared export.

An identical implementation was extracted to common/semantic-utils.js and exported precisely for reuse. This local copy (and matching ones in bruToJson.js and envToJson.js) should import from the shared module instead of redefining it.

♻️ Proposed fix
+const { extractDescription } = require('./common/semantic-utils');
+
-const extractDescription = (pair) => {
-  if (!_.isString(pair.value)) {
-    return;
-  }
-  const tripleMatch = pair.value.match(/\s*@description\('''([\s\S]*?)'''\)\s*$/);
-  if (tripleMatch) {
-    pair.description = tripleMatch[1].trim();
-    pair.value = pair.value.slice(0, -tripleMatch[0].length).trim();
-    return;
-  }
-  const doubleMatch = pair.value.match(/\s*@description\("([^"]*)"\)\s*$/);
-  if (doubleMatch) {
-    pair.description = doubleMatch[1].trim();
-    pair.value = pair.value.slice(0, -doubleMatch[0].length).trim();
-    return;
-  }
-};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-lang/v2/src/collectionBruToJson.js` around lines 82 - 98,
Remove the local duplicated extractDescription function and instead import and
use the shared exported helper from the common semantic utilities module; locate
the local definition named extractDescription in this file and delete it, add an
import for the exported extractDescription from the shared module (the same
symbol used by common/semantic-utils), and ensure references in this file (and
the other duplicates in bruToJson.js/envToJson.js) call that imported
extractDescription so behavior remains identical.
packages/bruno-lang/v2/tests/envToJson.spec.js (1)

38-59: New test covers the happy path but misses key edge cases.

Two gaps worth adding:

  1. Disabled vars with descriptions~url: value @description("desc") should be parsed as enabled: false with description intact. This is a likely regression point.
  2. secret field not asserted — all other tests verify secret: false for non-secret vars; using toMatchObject silently omits that check.
♻️ Suggested additions
+  it('should parse `@description` on disabled var', () => {
+    const input = `
+vars {
+  ~url: http://localhost:3000 `@description`("Disabled endpoint")
+}`;
+    const output = parser(input);
+    expect(output.variables[0]).toMatchObject({
+      name: 'url',
+      value: 'http://localhost:3000',
+      enabled: false,
+      secret: false,
+      description: 'Disabled endpoint'
+    });
+  });

As per coding guidelines: "Cover both the 'happy path' and the realistically problematic paths."

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

In `@packages/bruno-lang/v2/tests/envToJson.spec.js` around lines 38 - 59, Update
the test for parsing `@description` to include the two missing edge cases: add a
case where a disabled variable (e.g., "~url: http://... `@description`('desc')")
is parsed and assert that parser(...) returns that variable with enabled: false
and description preserved (check output.variables[n].enabled and .description);
also assert the secret field for non-secret vars (e.g.,
expect(output.variables[n].secret).toBe(false)) instead of relying solely on
toMatchObject so the secret flag is explicitly verified. Use the same parser
invocation and output.variables indexing used in the spec to locate where to add
these assertions.
🤖 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/EnvironmentVariablesTable/StyledWrapper.js`:
- Around line 45-58: The CSS rules for the description column are nested inside
the table block so the compiled selector becomes `table.has-description-column
td` which never matches because the `has-description-column` class is on the
StyledWrapper wrapper, not the table; move that whole block out of the `table {
... }` scope and change the selector to target the table as a descendant of the
wrapper (e.g. use `&.has-description-column table td { &:nth-child(4) { ... }
&:nth-child(5) { ... } &:nth-child(6) { ... } }`) so the nth-child(4/5/6) rules
apply when the wrapper has the `has-description-column` class.

In `@packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js`:
- Around line 193-216: MultipartFormParams is setting fixed column widths that
sum to 110% when showDescriptionColumn is true (resulting in horizontal
overflow); update the columns definition (the array that includes the column
with key 'description') to avoid hard-coding widths that force overflow—either
remove the explicit width on the 'value' column so it can flex (matching
RequestHeaders/FormUrlEncodedParams behavior) or reduce the widths of existing
columns proportionally when showDescriptionColumn is true; adjust the
render/column objects for keys like 'value' and 'description' in the
MultipartFormParams component so the table no longer exceeds 100% width.

In `@packages/bruno-lang/v2/src/jsonToCollectionBru.js`:
- Around line 8-15: getDescriptionSuffix currently embeds raw newlines and
mismatched escapes into BRU output causing parser breakage; update
getDescriptionSuffix to escape newlines (replace \n with \\n) and preserve
backslashes (\\\\) when building the `@description`(...) token, and update the
parser by implementing an extractDescription function that unescapes \\n →
newline and \\\\ → \\ when reading descriptions; additionally, adjust the
grammar by adding an optional descriptionannotation? to the pair rule (mirroring
the contenttypeannotation change on multilinetextblock) so description
annotations are allowed after multiline values, and ensure getValueString and
the parser consistently round-trip these escaped sequences.

---

Outside diff comments:
In `@packages/bruno-app/src/components/EnvironmentVariablesTable/index.js`:
- Around line 63-66: The resize handler currently hardcodes nextColumnKey =
'value', causing wrong column targeting when showDescriptionColumn is true;
update the logic in the resize start (where startX, startWidth, nextCell are
set) to derive nextColumnKey from the actual next sibling column element or from
the columns order (e.g., read a data-column-key attribute on nextCell or use the
columns array to find the column after currentCell) so the resize updates the
correct column (name -> value or name -> description depending on
showDescriptionColumn) and adjust any code paths that reference nextColumnKey
accordingly.
- Around line 70-79: handleMouseMove currently calls setColumnWidths with an
object containing only [columnKey] and [nextColumnKey], which overwrites the
entire widths state and drops the existing description width; change the update
to merge with the existing state (e.g., use functional setState and spread prev:
setColumnWidths(prev => ({ ...prev, [columnKey]: ..., [nextColumnKey]: ... })))
so description (and any other keys) are preserved; locate handleMouseMove and
the setColumnWidths call, and use the functional updater pattern referencing
columnKey, nextColumnKey, startWidth, nextColumnStartWidth and MIN_COLUMN_WIDTH
to apply the clamped widths without replacing other keys.

In
`@packages/bruno-app/src/components/EnvironmentVariablesTable/StyledWrapper.js`:
- Around line 66-71: The header cell CSS selector (thead td) is creating double
seams because both border-right and border-left are applied; remove border-left
from the thead td rule (or keep only border-left and remove border-right) and
instead add a single-border rule for the table edge using a pseudo/edge selector
(e.g., thead td:first-child or thead td:last-child) to ensure only one border
between adjacent header cells; update the StyledWrapper.js thead td rule
accordingly and adjust any tests/styles that expect the previous double-border
behavior.

---

Duplicate comments:
In `@packages/bruno-app/src/components/EnvironmentVariablesTable/index.js`:
- Around line 566-583: The Description toggle button styling is inconsistent:
update the button that toggles showDescriptionColumn (the onClick using
setShowDescriptionColumn and the label rendering showDescriptionColumn ? 'Hide
Description' : 'Description') to use the same classNames as other components
(replace "submit description-toggle ml-2" with "btn-action text-link
select-none" and preserve spacing utility classes like ml-2 if needed) so its
visual appearance matches the rest of the PR.

In `@packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js`:
- Around line 96-104: The description-toggle button is rendered above the params
table but should be grouped with the "Bulk Edit" controls below the table to
match RequestHeaders and FolderSettings/Headers; remove the existing div that
renders the toggle (the element using showDescriptionColumn and
setShowDescriptionColumn) and instead render that same toggle button alongside
the "Bulk Edit" control in the footer/controls area beneath the table (the same
container used for Bulk Edit), preserving the onClick handler that toggles
setShowDescriptionColumn and the displayed label based on showDescriptionColumn
so the behavior is unchanged but placement matches sibling components.

In `@packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js`:
- Around line 229-237: The Description toggle div is placed above the
EditableTable causing inconsistent UI; move the entire <div className="flex
justify-end mt-2 mb-2"> block (the button that uses showDescriptionColumn and
setShowDescriptionColumn) so it appears immediately after the <EditableTable />
component in this file, mirroring placement used in Headers and VarsTable;
ensure the onClick still toggles showDescriptionColumn and no other JSX is
changed.

In `@packages/bruno-lang/v2/src/bruToJson.js`:
- Around line 163-179: The local extractDescription function is duplicated and
should be removed and replaced by importing the shared implementation from
common/semantic-utils.js; delete the extractDescription definition in
bruToJson.js, add an import for extractDescription from the shared module, and
update any local references to use the imported extractDescription (ensure the
import symbol name matches usages). Also remove any lodash/string checks that
the shared function already handles so there are no redundant guards.

In `@packages/bruno-lang/v2/src/common/semantic-utils.js`:
- Around line 7-25: extractDescription currently strips `@description` blocks but
doesn't reverse the escapes added by getDescriptionSuffix, causing saved
descriptions to be corrupted; modify extractDescription (the function named
extractDescription) to unescape escaped backslashes and double-quotes when
assigning pair.description for both tripleMatch and doubleMatch cases (e.g.,
convert \\ to \ and \" to "), and then trim and remove the matched suffix from
pair.value as before so round-trip serialization/deserialization preserves
original text.

In `@packages/bruno-lang/v2/src/jsonToBru.js`:
- Around line 9-16: The getDescriptionSuffix function is duplicated (also
present in jsonToCollectionBru.js); extract it into a shared utility module
(e.g., export a getDescriptionSuffix function from a new or existing utils
file), replace the inline implementation in both jsonToBru.js and
jsonToCollectionBru.js with imports of that shared function, and ensure the
exported function preserves the current behavior (handling null/empty,
triple-quote detection, and proper backslash/quote escaping) and is used
wherever getDescriptionSuffix was referenced.

---

Nitpick comments:
In
`@packages/bruno-app/src/components/CollectionSettings/Vars/VarsTable/index.js`:
- Around line 33-49: descriptionColumn is rebuilt on every render even when
hidden; wrap its creation in useMemo and only return the object when
showDescriptionColumn is true. Use React.useMemo(() => { ... }, [storedTheme,
onSave, collection]) so the render closure passed to MultiLineEditor captures
stable storedTheme, onSave and collection, and skip creating descriptionColumn
when showDescriptionColumn is false (return null/undefined) so columns assembly
can conditionally include it without recreating the function each render.

In `@packages/bruno-app/src/components/EnvironmentVariablesTable/index.js`:
- Around line 427-432: Summary: descriptionMatch uses a redundant truthy check
before typeof; remove it. Update the filter expression in the
EnvironmentVariablesTable component to compute descriptionMatch by only checking
typeof variable.description === 'string' (and then calling
.toLowerCase().includes(query) if true), so empty strings aren't specially
excluded and the logic is clearer; adjust the variable named descriptionMatch
and the return that uses nameMatch, valueMatch, descriptionMatch accordingly.

In
`@packages/bruno-app/src/components/EnvironmentVariablesTable/StyledWrapper.js`:
- Around line 183-189: The .secret-column currently uses !important while
.actions-column does not, causing inconsistent specificity; update
StyledWrapper.js so both column rules use consistent specificity — either remove
the !important from .secret-column and increase selector specificity for both
(.secret-column and .actions-column) to override the nth-child width, or add
!important to .actions-column as well; locate and change the styles for
.secret-column and .actions-column so they match in specificity and reliably
override the nth-child selector.

In `@packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/index.js`:
- Around line 38-55: The descriptionColumn object (including its render closure
that returns MultiLineEditor) is recreated on every render which can break
EditableTable’s column reference equality optimizations; wrap the
descriptionColumn creation in React's useMemo (and add useMemo to the existing
React imports) so the column reference is stable unless its dependencies change
(e.g., storedTheme, onSave, collection, folder); ensure the memo dependency
array includes storedTheme, onSave, collection and folder (and any values used
inside render) so the column is recomputed only when needed.

In `@packages/bruno-app/src/components/RequestPane/QueryParams/index.js`:
- Around line 67-105: descriptionColumnQuery and descriptionColumnPath are
almost identical and should be merged via a factory to avoid duplication and
accidental drift; create a helper like createDescriptionColumn({
onChangeOverride }) or createDescriptionColumn({ isPath }) that returns the
column object used for both cases, keeping shared properties (key, name,
placeholder, width, theme, onSave, allowNewlines, onRun, collection, item,
placeholder logic) and injecting the differing onChange behavior (either the
passed onChange or a wrapper that calls handlePathParamChange(row.uid,
'description', newValue)); update the uses of descriptionColumnQuery and
descriptionColumnPath to call this factory and remove the duplicated
definitions.
- Around line 187-196: The toggle button currently shows "Description" when the
column is hidden; update the button label logic in the QueryParams component to
be explicit by using the showDescriptionColumn state so it reads "Show
Description" when showDescriptionColumn is false and "Hide Description" when
true; locate the JSX that renders the button (the onClick uses
setShowDescriptionColumn) and replace the existing ternary/string with a clear
"Show Description" / "Hide Description" pair so the action is unambiguous to
users.
- Around line 92-97: descriptionColumnPath.render currently receives an onChange
prop from EditableTable but intentionally ignores it by calling
handlePathParamChange instead; add a concise inline comment next to the
MultiLineEditor onChange (or above descriptionColumnPath.render) stating that
the prop is deliberately bypassed because path params use a per-field dispatch
via handlePathParamChange(row.uid, 'description', ...) so future readers won't
be confused. Keep the comment to one line and reference the use of
handlePathParamChange and row.uid for clarity.
- Around line 39-53: handlePathParamChange is never truly memoized because
pathParams is recreated each render; either move the find() lookup into the
reducer action (so handlePathParamChange sends the rowUid and key/value to
dispatch and let updatePathParam resolve the pathParam by uid), or stabilize
pathParams with useMemo before using it in the useCallback dependency array.
Specifically, either change handlePathParamChange to dispatch({ type:
updatePathParam, payload: { uid: rowUid, key, value }, itemUid: item.uid,
collectionUid: collection.uid }) and resolve the item in the reducer, or wrap
the pathParams filter in useMemo (e.g. const pathParams = useMemo(() =>
originalPathParams.filter(...), [originalPathParams]) ) and keep the existing
handlePathParamChange that references pathParams, dispatch, item.uid,
collection.uid.

In `@packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js`:
- Around line 48-66: descriptionColumn is recreated on every render; wrap its
definition in useMemo and include the dependencies [storedTheme, onSave,
handleRun, collection, item] so the rendered MultiLineEditor props are stable,
and add useMemo to the import list; keep the same shape and return value but
memoize the const descriptionColumn = useMemo(() => ({ key: 'description', name:
'Description', placeholder: 'Description', width: '25%', render: ({ value,
onChange }) => (<MultiLineEditor ... />) }), [storedTheme, onSave, handleRun,
collection, item]).

In `@packages/bruno-lang/v2/src/collectionBruToJson.js`:
- Around line 82-98: Remove the local duplicated extractDescription function and
instead import and use the shared exported helper from the common semantic
utilities module; locate the local definition named extractDescription in this
file and delete it, add an import for the exported extractDescription from the
shared module (the same symbol used by common/semantic-utils), and ensure
references in this file (and the other duplicates in bruToJson.js/envToJson.js)
call that imported extractDescription so behavior remains identical.

In `@packages/bruno-lang/v2/src/common/semantic-utils.js`:
- Around line 133-147: In mapPairListToKeyValPairsMultipart, remove the
redundant unconditional call to extractDescription for each pair (currently
after mapping pairs from mapPairListToKeyValPairs) and instead only call
extractDescription when parseEnabled is false; reference
mapPairListToKeyValPairs (which already extracts descriptions when parseEnabled
=== true), the local parseEnabled flag, and the extractDescription function to
implement the conditional so descriptions are only stripped once.

In `@packages/bruno-lang/v2/src/jsonToCollectionBru.js`:
- Around line 8-15: getDescriptionSuffix is duplicated across
jsonToCollectionBru.js, jsonToBru.js, and jsonToEnv.js; extract this function
into a shared utility module (e.g., common/semantic-utils or utils) and replace
the copies with imports. Create a new exported function getDescriptionSuffix in
the shared module using the same implementation (preserve string escaping logic
and return values), update jsonToCollectionBru.js, jsonToBru.js, and
jsonToEnv.js to import getDescriptionSuffix and remove their local
implementations, and run tests/lint to ensure no references remain to the old
local functions.

In `@packages/bruno-lang/v2/src/jsonToEnv.js`:
- Around line 4-11: The getDescriptionSuffix function is duplicated in
jsonToEnv.js and jsonToBru.js; extract it into
packages/bruno-lang/v2/src/utils.js as an exported helper (preserve its current
logic and escaping behavior and keep the function name getDescriptionSuffix),
then remove the duplicate definitions from jsonToEnv.js and jsonToBru.js and
replace them with imports that import { getDescriptionSuffix } from './utils';
so both modules use the single shared implementation.

In `@packages/bruno-lang/v2/tests/envToJson.spec.js`:
- Around line 38-59: Update the test for parsing `@description` to include the two
missing edge cases: add a case where a disabled variable (e.g., "~url:
http://... `@description`('desc')") is parsed and assert that parser(...) returns
that variable with enabled: false and description preserved (check
output.variables[n].enabled and .description); also assert the secret field for
non-secret vars (e.g., expect(output.variables[n].secret).toBe(false)) instead
of relying solely on toMatchObject so the secret flag is explicitly verified.
Use the same parser invocation and output.variables indexing used in the spec to
locate where to add these assertions.

Comment on lines +45 to 58
&.has-description-column td {
&:nth-child(4) {
vertical-align: top;
white-space: pre-wrap;
word-wrap: break-word;
word-break: break-word;
}
&:nth-child(5) {
width: 80px;
}
&:nth-child(6) {
width: 60px;
}
}
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find and examine the StyledWrapper.js file
fd -n "StyledWrapper.js" packages/bruno-app/src/components/EnvironmentVariablesTable/

Repository: usebruno/bruno

Length of output: 288


🏁 Script executed:

# Read the StyledWrapper.js file to see the CSS structure
cat -n packages/bruno-app/src/components/EnvironmentVariablesTable/StyledWrapper.js

Repository: usebruno/bruno

Length of output: 5635


🏁 Script executed:

# Also check the index.js to see where the class is applied
rg -n "has-description-column" packages/bruno-app/src/components/EnvironmentVariablesTable/ -A 3 -B 3

Repository: usebruno/bruno

Length of output: 1539


CSS selector table.has-description-column td won't match — styles won't apply.

The rule at line 45 is nested inside table { }, so styled-components compiles &.has-description-column to table.has-description-column td. However, the has-description-column class is applied to the wrapper <StyledWrapper> div (index.js line 436), not to the <table> element. The selector requires the class to be on the table itself, so these rules are dead CSS.

Move the block outside the table scope to target table as a descendant:

    }

-    &.has-description-column td {
-      &:nth-child(4) {
-        vertical-align: top;
-        white-space: pre-wrap;
-        word-wrap: break-word;
-        word-break: break-word;
-      }
-      &:nth-child(5) {
-        width: 80px;
-      }
-      &:nth-child(6) {
-        width: 60px;
-      }
-    }
   }
+
+  &.has-description-column table td {
+    &:nth-child(4) {
+      vertical-align: top;
+      white-space: pre-wrap;
+      word-wrap: break-word;
+      word-break: break-word;
+    }
+    &:nth-child(5) {
+      width: 80px;
+    }
+    &:nth-child(6) {
+      width: 60px;
+    }
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/EnvironmentVariablesTable/StyledWrapper.js`
around lines 45 - 58, The CSS rules for the description column are nested inside
the table block so the compiled selector becomes `table.has-description-column
td` which never matches because the `has-description-column` class is on the
StyledWrapper wrapper, not the table; move that whole block out of the `table {
... }` scope and change the selector to target the table as a descendant of the
wrapper (e.g. use `&.has-description-column table td { &:nth-child(4) { ... }
&:nth-child(5) { ... } &:nth-child(6) { ... } }`) so the nth-child(4/5/6) rules
apply when the wrapper has the `has-description-column` class.

Comment on lines +193 to +216
},
...(showDescriptionColumn
? [
{
key: 'description',
name: 'Description',
placeholder: 'Description',
width: '25%',
render: ({ value, onChange }) => (
<MultiLineEditor
value={value || ''}
theme={storedTheme}
onSave={onSave}
onChange={onChange}
allowNewlines={true}
onRun={handleRun}
collection={collection}
item={item}
placeholder={!value ? 'Description' : ''}
/>
)
}
]
: [])
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

🧩 Analysis chain

🏁 Script executed:

# Find and examine EditableTable component
fd -e js -e jsx -e ts -e tsx -g '*EditableTable*' -o

Repository: usebruno/bruno

Length of output: 174


🏁 Script executed:

# Read the actual file context around lines 193-216
cat -n packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js | sed -n '185,225p'

Repository: usebruno/bruno

Length of output: 1352


🏁 Script executed:

# Check if description column is added elsewhere with width adjustments
rg -n "showDescriptionColumn" packages/bruno-app/src/components/RequestPane/ -A 5 -B 5

Repository: usebruno/bruno

Length of output: 19151


🏁 Script executed:

# Get the full columns array definition for MultipartFormParams
cat -n packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js | sed -n '150,220p'

Repository: usebruno/bruno

Length of output: 2580


🏁 Script executed:

# Check how other components define their description column
rg -A 15 "descriptionColumn.*=" packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js | head -30

Repository: usebruno/bruno

Length of output: 513


🏁 Script executed:

# Search for EditableTable component definition to understand width handling
fd -e js -e jsx -e ts -e tsx | xargs rg -l "EditableTable" | xargs rg -l "class EditableTable|const EditableTable|export.*EditableTable" | head -5

Repository: usebruno/bruno

Length of output: 278


🏁 Script executed:

# Read the EditableTable component to understand width handling
wc -l packages/bruno-app/src/components/EditableTable/index.js

Repository: usebruno/bruno

Length of output: 118


🏁 Script executed:

# Get the relevant parts of EditableTable that handle column widths
cat -n packages/bruno-app/src/components/EditableTable/index.js | head -150

Repository: usebruno/bruno

Length of output: 5797


🏁 Script executed:

# Check if there's a CSS file for EditableTable
find packages/bruno-app/src/components/EditableTable -type f \( -name "*.css" -o -name "*.scss" \)

Repository: usebruno/bruno

Length of output: 40


🏁 Script executed:

# Check all the columns definitions in MultipartFormParams to see the full picture
cat -n packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js | sed -n '115,150p'

Repository: usebruno/bruno

Length of output: 1441


🏁 Script executed:

# Find CSS files for EditableTable
find packages/bruno-app/src/components/EditableTable -name "*.css" -o -name "*.scss"

Repository: usebruno/bruno

Length of output: 40


🏁 Script executed:

# Check StyledWrapper to see how widths are applied
cat packages/bruno-app/src/components/EditableTable/StyledWrapper.js

Repository: usebruno/bruno

Length of output: 4633


🏁 Script executed:

# Check where column widths are actually applied in the render
cat -n packages/bruno-app/src/components/EditableTable/index.js | sed -n '200,300p'

Repository: usebruno/bruno

Length of output: 3903


🏁 Script executed:

# Check how EditableTable applies column widths in the rendering
cat -n packages/bruno-app/src/components/EditableTable/index.js | sed -n '300,350p'

Repository: usebruno/bruno

Length of output: 2275


🏁 Script executed:

# Look for where column width is actually set on table cells
cat -n packages/bruno-app/src/components/EditableTable/index.js | sed -n '350,450p'

Repository: usebruno/bruno

Length of output: 4431


🏁 Script executed:

# Verify that other similar table components use identical width patterns
for file in packages/bruno-app/src/components/RequestPane/*/index.js; do
  echo "=== $(basename $(dirname $file)) ==="
  rg -A 5 "width:.*%" "$file" | head -20
done

Repository: usebruno/bruno

Length of output: 2521


🏁 Script executed:

# Get complete column width definitions for all components with description columns
for file in FormUrlEncodedParams RequestHeaders QueryParams Vars MultipartFormParams; do
  echo "=== $file ==="
  rg -B 10 "width: '25%'" "packages/bruno-app/src/components/RequestPane/$file/index.js" | grep -E "(key:|width:)" | head -20
done

Repository: usebruno/bruno

Length of output: 396


🏁 Script executed:

# Check RequestHeaders full widths
rg -B 15 "descriptionColumn" packages/bruno-app/src/components/RequestPane/RequestHeaders/index.js | grep -E "(key:|width:)" | head -30

Repository: usebruno/bruno

Length of output: 40


🏁 Script executed:

# Get full column definitions for RequestHeaders to see base columns + description
cat -n packages/bruno-app/src/components/RequestPane/RequestHeaders/index.js | sed -n '80,135p'

Repository: usebruno/bruno

Length of output: 1733


🏁 Script executed:

# Get full column definitions for FormUrlEncodedParams
cat -n packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js | sed -n '40,90p'

Repository: usebruno/bruno

Length of output: 1561


Column widths create horizontal scrolling when Description column is shown.

MultipartFormParams sets explicit widths: 30% + 35% + 20% + 25% = 110%. With table-layout: fixed, this causes the table to overflow the container. The container's overflow: auto allows horizontal scrolling, but this may not be the intended UX.

Other similar components (RequestHeaders, FormUrlEncodedParams) avoid this by not setting explicit width on the Value column, allowing it to flex. Either reduce the widths of existing columns proportionally when showDescriptionColumn is true, or verify that horizontal scrolling is acceptable behavior.

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

In `@packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js`
around lines 193 - 216, MultipartFormParams is setting fixed column widths that
sum to 110% when showDescriptionColumn is true (resulting in horizontal
overflow); update the columns definition (the array that includes the column
with key 'description') to avoid hard-coding widths that force overflow—either
remove the explicit width on the 'value' column so it can flex (matching
RequestHeaders/FormUrlEncodedParams behavior) or reduce the widths of existing
columns proportionally when showDescriptionColumn is true; adjust the
render/column objects for keys like 'value' and 'description' in the
MultipartFormParams component so the table no longer exceeds 100% width.

Comment on lines +8 to +15
const getDescriptionSuffix = (item) => {
const desc = item && item.description && String(item.description).trim();
if (!desc) return '';
if (desc.includes('\'\'\'')) {
return ' @description("' + desc.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '")';
}
return ' @description(\'\'\'' + desc.replace(/\\/g, '\\\\') + '\'\'\')';
};
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

Multiline descriptions will produce malformed BRU files.

When desc contains a literal newline (users can enter multiline text via the MultiLineEditor), getDescriptionSuffix returns a string with an embedded \n, e.g.:

Authorization: Bearer token `@description`('''Line 1
Line 2''')

The BRU grammar's singlelinevalue = valuechar* stops at the first newline (valuechar = ~(nl | tagend) any), so the parser captures only Bearer token @description('''Line 1 and then fails on Line 2'''). The file is silently corrupted or causes a hard parse error on next load.

A secondary issue: for variable items whose value is already a triple-quoted multiline block, getValueString returns '''\n content\n'''. Appending @description(...) after that produces:

varname: '''
  content
''' `@description`("desc")

The pair grammar rule allows only st* after value, so @description(...) in this position is not recognized — the description is silently dropped.

Fix: Escape newlines in descriptions before embedding in the BRU line, and unescape them on parse:

🐛 Proposed fix for getDescriptionSuffix
 const getDescriptionSuffix = (item) => {
   const desc = item && item.description && String(item.description).trim();
   if (!desc) return '';
+  // Escape newlines so the annotation stays on one line
+  const safeDesc = desc.replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/\r/g, '');
-  if (desc.includes('\'\'\'')) {
-    return ' `@description`("' + desc.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '")';
+  if (safeDesc.includes('\'\'\'')) {
+    return ' `@description`("' + safeDesc.replace(/"/g, '\\"') + '")';
   }
-  return ' `@description`(\'\'\'' + desc.replace(/\\/g, '\\\\') + '\'\'\')';
+  return ' `@description`(\'\'\'' + safeDesc + '\'\'\')';
 };

extractDescription must correspondingly unescape \\n → \n and \\\\ → \\ after capturing the group (also fixes the existing round-trip escaping mismatch).

The description-on-multiline-value case requires a grammar change (adding an optional descriptionannotation? to the pair rule, similarly to how contenttypeannotation was added to multilinetextblock).

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

In `@packages/bruno-lang/v2/src/jsonToCollectionBru.js` around lines 8 - 15,
getDescriptionSuffix currently embeds raw newlines and mismatched escapes into
BRU output causing parser breakage; update getDescriptionSuffix to escape
newlines (replace \n with \\n) and preserve backslashes (\\\\) when building the
`@description`(...) token, and update the parser by implementing an
extractDescription function that unescapes \\n → newline and \\\\ → \\ when
reading descriptions; additionally, adjust the grammar by adding an optional
descriptionannotation? to the pair rule (mirroring the contenttypeannotation
change on multilinetextblock) so description annotations are allowed after
multiline values, and ensure getValueString and the parser consistently
round-trip these escaped sequences.

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.

1 participant

Comments