Skip to content

Environment's as tabs#6407

Merged
bijin-bruno merged 11 commits intousebruno:mainfrom
naman-bruno:feat/env-tabs
Dec 18, 2025
Merged

Environment's as tabs#6407
bijin-bruno merged 11 commits intousebruno:mainfrom
naman-bruno:feat/env-tabs

Conversation

@naman-bruno
Copy link
Collaborator

@naman-bruno naman-bruno commented Dec 15, 2025

Description

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.

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

Publishing to New Package Managers

Please see here for more information.

Summary by CodeRabbit

  • New Features

    • Environment and Global Environment settings now open in dedicated tabs with save/close confirmation.
  • Improvements

    • Inline creation, renaming and editing of environments with validation, draft autosave and explicit save flows.
    • Better handling and UX for non-string variable values; refreshed styling for environment lists and variable editor.
    • Workspace tab label updated to "Global Environments".
  • Tests

    • E2E tests updated to use tab-based flows and new selectors.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 15, 2025

Walkthrough

Moves environment and global-environment configuration from modal dialogs into tab-based UI, replacing modal visibility actions with addTab usage, adding environment draft reducers, removing many modal components, updating tab rendering and close-confirm flows, and updating tests/helpers to operate on tabs.

Changes

Cohort / File(s) Summary
Modal → Tab Core
packages/bruno-app/src/components/CollectionSettings/Overview/Info/index.js, packages/bruno-app/src/components/Environments/EnvironmentSelector/index.js, packages/bruno-app/src/providers/Hotkeys/index.js
Replaced modal-open dispatches with addTab for environment-settings / global-environment-settings; removed modal visibility actions and local modal renders.
Tabs & Tab Rendering
packages/bruno-app/src/providers/ReduxStore/slices/tabs.js, packages/bruno-app/src/components/RequestTabPanel/index.js, packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js, packages/bruno-app/src/components/RequestTabs/RequestTab/index.js, packages/bruno-app/src/components/RequestTabs/index.js
Added new tab types and icons/labels, marked them non-replaceable, added render branches for environment tabs, and wired close-confirm flows that persist/clear drafts before closing.
Redux: drafts & app cleanup
packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js, packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js, packages/bruno-app/src/providers/ReduxStore/slices/app.js
Added setEnvironmentsDraft/clearEnvironmentsDraft and setGlobalEnvironmentDraft/clearGlobalEnvironmentDraft; removed updateEnvironmentSettingsModalVisibility and updateGlobalEnvironmentSettingsModalVisibility reducers/actions.
Environment Settings (collection)
packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js, packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/*, packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/*, packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/*
Reworked modal-wrapped flows into inline/tab-driven UI: inline create/rename/edit, draft sync, removal of onClose props, refactored validation/row behavior, and new styled wrappers.
Workspace / Global Environments (new tab)
packages/bruno-app/src/components/Environments/GlobalEnvironmentSettings/index.js, packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/*, packages/bruno-app/src/components/WorkspaceHome/index.js
Added GlobalEnvironmentSettings component, updated labels to "Global Environments", integrated global env draft handling and workspace wiring.
Global Environments — deletions
packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/* (many files removed)
Removed multiple modal-based components (CreateEnvironment, CopyEnvironment, DeleteEnvironment, RenameEnvironment, EnvironmentList/Details/Variables, StyledWrappers) in favor of tab-driven WorkspaceEnvironments.
Confirm-close UI
packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js, packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/*
Added ConfirmCloseEnvironment modal used by tab close flows to handle unsaved drafts (global vs collection messaging).
Styling updates
packages/bruno-app/src/components/Environments/EnvironmentSettings/StyledWrapper.js, .../EnvironmentList/StyledWrapper.js, .../EnvironmentDetails/StyledWrapper.js, .../EnvironmentVariables/StyledWrapper.js, packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/StyledWrapper.js
Introduced/expanded styled-components for inline edit layouts, scrolling table container, action buttons, sidebar and overflow behavior.
Tests & Test Utilities
tests/**, tests/utils/page/actions.ts, tests/utils/page/locators.ts
Migrated tests from modal-based assertions to tab-based flows: updated selectors, replaced modal-close with hovering tab + request-tab-close-icon, added addEnvironmentVariables helper, added EnvironmentType and updated closeEnvironmentPanel/createEnvironment signatures.
Misc: small updates
packages/bruno-app/src/utils/common/regex.js, packages/bruno-app/src/components/RequestTabs/index.js, packages/bruno-app/src/components/RequestTabPanel/index.js
Improved validation messages, removed an early RequestTabs error guard, and ensured environment tab render precedence in RequestTabPanel.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as App UI
    participant Tabs as Tabs system
    participant Redux as Redux store
    participant Env as Environment editor

    User->>UI: Open Environments / Global Environments
    UI->>Tabs: dispatch addTab({ type: environment-settings|global-environment-settings, ... })
    Tabs->>Redux: create/activate tab
    Redux->>UI: focusedTab state update
    UI->>Env: render EnvironmentSettings (tab content)
    User->>Env: Edit variables/name
    Env->>Redux: dispatch setEnvironmentsDraft / setGlobalEnvironmentDraft
    Redux->>Redux: store draft
    User->>Env: Save (button or hotkey)
    Env->>window: emit 'environment-save'
    Env->>Redux: dispatch saveEnvironment / saveGlobalEnvironment
    Redux->>Redux: persist and dispatch clearEnvironmentsDraft / clearGlobalEnvironmentDraft
    User->>UI: Close tab
    UI->>Redux: detect draft -> show ConfirmCloseEnvironment
    alt Save then Close
      UI->>Redux: dispatch save... then clear draft
    else Discard
      UI->>Redux: dispatch clear draft
    end
    UI->>Tabs: dispatch closeTab
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–75 minutes

  • Areas to focus:
    • Draft lifecycle (set/clear/restore) in collections/global-environments slices and interactions with save/close flows.
    • Tab routing/render precedence in RequestTabPanel and RequestTabs close-confirm integration.
    • Deleted modal components: ensure no remaining imports reference removed files.
    • Tests/helpers: verify updated selectors, new signatures (EnvironmentType), and added helpers (addEnvironmentVariables).

Possibly related PRs

Suggested reviewers

  • lohit-bruno
  • helloanoop

Poem

Tabs unfurl where modals used to hide,
Drafts tucked safe on Redux' shifting tide.
Click, edit, save — no popup in your way,
Close with care, confirm or discard away.
Small code, big flow — hooray for a tidier day. 🎉

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Environment's as tabs' accurately reflects the core objective: converting environment settings from modal-based to tab-based navigation. It's concise and specific about the main change.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 (2)
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (1)

168-170: Incorrect resetForm parameter structure.

Formik's resetForm expects { values: ... } shape. The current code passes { originalEnvironmentVariables } which creates { originalEnvironmentVariables: [...] } instead of { values: [...] }.

   const handleReset = () => {
-    formik.resetForm({ originalEnvironmentVariables });
+    formik.resetForm({ values: originalEnvironmentVariables });
   };
packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (1)

263-278: Draft not cleared on reset.

The handleReset function resets the form and sets isModified to false, but doesn't dispatch clearGlobalEnvironmentsDraft(). This leaves stale draft data in Redux, which could cause issues when navigating away and back.

 const handleReset = () => {
   const originalVars = environment.variables || [];
   const resetValues = [
     ...originalVars,
     {
       uid: uuid(),
       name: '',
       value: '',
       type: 'text',
       secret: false,
       enabled: true
     }
   ];
   formik.resetForm({ values: resetValues });
   setIsModified(false);
+  dispatch(clearGlobalEnvironmentsDraft());
 };
🧹 Nitpick comments (18)
packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/StyledWrapper.js (1)

215-266: Duplicate input/actions styles in .creating block.

The .environment-name-input and .inline-actions styles (lines 216-234) duplicate what's already defined in .rename-container (lines 194-212). Consider extracting these shared styles or relying on CSS inheritance from the parent selectors.

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

222-284: Consider extracting repeated addTab dispatch logic.

The same addTab dispatch pattern appears four times (lines 226-232, 242-248, 258-264, 275-281). Consider extracting to helper functions:

const openEnvironmentSettingsTab = () => {
  dispatch(
    addTab({
      uid: `${collection.uid}-environment-settings`,
      collectionUid: collection.uid,
      type: 'environment-settings'
    })
  );
};

const openGlobalEnvironmentSettingsTab = () => {
  dispatch(
    addTab({
      uid: `${collection.uid}-global-environment-settings`,
      collectionUid: collection.uid,
      type: 'global-environment-settings'
    })
  );
};

Then use as onEnvironmentCreated={openEnvironmentSettingsTab} etc.

packages/bruno-app/src/providers/ReduxStore/slices/tabs.js (1)

24-30: Singleton & non-preview behavior for env/security tabs looks right

Marking security-settings, environment-settings, and global-environment-settings as nonReplaceableTabTypes correctly:

  • Avoids opening duplicate tabs per collectionUid (re-activates existing instead)
  • Makes these tabs non-preview by default

That matches the mental model of “one settings view per collection”. If you also want collection-settings / collection-overview to behave as singletons, they could be added to this list later, but that’s optional and not required by this change.

Also applies to: 38-44, 63-66, 84-87

packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js (1)

14-24: EnvironmentList props match new flow; originalEnvironmentVariables now looks unused

Dropping onClose from the props and from how EnvironmentDetails is used is consistent with moving the close/navigation responsibility up to the tab system instead of the inner details view.

However, originalEnvironmentVariables is still tracked locally and passed into <EnvironmentDetails ... originalEnvironmentVariables={originalEnvironmentVariables} />, while the EnvironmentDetails in this path no longer accepts or uses that prop. If unsaved-changes logic has been moved fully into EnvironmentVariables, consider removing the originalEnvironmentVariables state and prop here to avoid dead parameters and confusion.

Also applies to: 96-100, 143-148

packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js (1)

1-192: Theme usage is solid; add explicit focus styles for better accessibility

Nice use of props.theme for all colors and borders here, which is consistent with the styled-components theming guidance (no raw CSS vars in this context). Based on learnings, this is the right pattern.

One thing to tighten up: input[type='text'] (and buttons via the global button rule plus subclasses) remove the browser outline (outline: none !important) and don’t introduce a custom focus visual. That makes it hard to see where focus is when tabbing.

Consider adding a clear :focus style (e.g., border or background change using theme tokens) instead of, or in addition to, removing the default outline, so keyboard users still get a visible focus indicator.

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

1-8: Wrapper is fine; clarify whether collection should be part of the API

GlobalEnvironmentSettingsTab is a thin wrapper over WorkspaceEnvironments and currently doesn’t take any props, which is fine for now.

Since RequestTabPanel is calling it as <GlobalEnvironmentSettingsTab collection={collection} />, consider either:

  • Updating this component to accept and (if needed later) use collection, or
  • Dropping the collection prop at the call site so the API surface matches the implementation.

Right now the extra prop is harmless but can be misleading when scanning the code.

packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (2)

21-21: Unused prop originalEnvironmentVariables.

The originalEnvironmentVariables prop is destructured but never referenced in the component body. Remove it if no longer needed.

-const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentVariables, collection }) => {
+const EnvironmentVariables = ({ environment, setIsModified, collection }) => {

125-150: Optimize draft comparison to avoid unnecessary Redux dispatches.

Using JSON.stringify for deep comparison works but is expensive for large variable sets. More importantly, the effect dispatches on every formik.values change, and the comparison logic could be simplified.

Consider memoizing the comparison or debouncing the draft sync to reduce Redux overhead during rapid typing.

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

43-58: Missing save handling for environment-settings tabs.

The save hotkey handles folder-settings and collection-settings but doesn't handle environment-settings or global-environment-settings tab types. Users might expect Ctrl+S to save environment changes when viewing those tabs.

Consider adding cases for the new tab types:

          } else if (activeTab.type === 'collection-settings') {
            dispatch(saveCollectionSettings(collection.uid));
+         } else if (activeTab.type === 'environment-settings') {
+           // Handle environment save via appropriate action
+         } else if (activeTab.type === 'global-environment-settings') {
+           // Handle global environment save via appropriate action
          }

66-66: Unnecessary dependency in useEffect array.

saveRequest is an imported action creator and doesn't change between renders. Remove it from the dependency array.

-  }, [activeTabUid, tabs, saveRequest, collections, dispatch]);
+  }, [activeTabUid, tabs, collections, dispatch]);
packages/bruno-app/src/components/EnvironmentSettingsTab/index.js (1)

30-32: Redundant null check.

Since environments defaults to [] on line 30, the !environments check on line 32 is redundant.

-  if (!environments || !environments.length) {
+  if (!environments.length) {
packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/index.js (2)

62-65: Redundant condition in environment change check.

If hasSelectedEnvironmentChanged is true (via isEqual), then selectedEnvironment.uid !== _selectedEnvironment?.uid is implicitly covered. The second condition is only useful if isEqual returned false but UIDs differ, which would be a data inconsistency.

-      if (hasSelectedEnvironmentChanged || selectedEnvironment.uid !== _selectedEnvironment?.uid) {
+      if (hasSelectedEnvironmentChanged) {
         setSelectedEnvironment(_selectedEnvironment);
       }

89-117: Duplicate click-outside handler pattern.

These two useEffects share identical logic for handling outside clicks. Consider extracting to a custom hook like useClickOutside(ref, callback, isActive).

packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/index.js (2)

23-49: Redundant length check and duplicate validation logic.

Line 28-30: If name.trim() === '' is false (line 24-26), then name.length >= 1 is guaranteed, making the check redundant.

Also, validateEnvironmentName is duplicated in EnvironmentList/index.js. Consider extracting to a shared utility.

 const validateEnvironmentName = (name) => {
   if (!name || name.trim() === '') {
     return 'Name is required';
   }

-  if (name.length < 1) {
-    return 'Must be at least 1 character';
-  }
-
   if (name.length > 255) {
     return 'Must be 255 characters or less';
   }

93-102: Potential UX issue with blur validation.

handleNameBlur validates and shows an error but doesn't auto-save or auto-cancel. Users might be confused when the error persists but clicking elsewhere doesn't resolve the state. Consider either auto-canceling on blur with invalid input or triggering save on blur with valid input.

packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (3)

127-153: Performance consideration with JSON serialization on every change.

This effect runs on every formik.values change and performs multiple JSON.stringify operations. For large environment variable lists, this could impact performance.

Consider debouncing the draft sync or using a more efficient comparison (e.g., shallow compare of array length + deep compare only when lengths match).


104-124: Draft restoration effect intentionally omits formik from deps.

The effect correctly avoids including formik in the dependency array to prevent infinite loops. However, this pattern is fragile. Adding a brief comment explaining this design decision would help future maintainers.

 // Restore draft values on mount or environment switch
+// Note: formik intentionally omitted from deps to prevent infinite loops
 React.useEffect(() => {

220-223: Race condition risk with setTimeout + setFieldValue.

Using setTimeout to append a new row after handleChange completes could cause race conditions if the user types quickly. Consider using Formik's setValues to batch the update atomically.

 if (isLastRow) {
-  const newVariable = {
-    uid: uuid(),
-    name: '',
-    value: '',
-    type: 'text',
-    secret: false,
-    enabled: true
-  };
-  setTimeout(() => {
-    formik.setFieldValue(formik.values.length, newVariable, false);
-  }, 0);
+  const newVariable = {
+    uid: uuid(),
+    name: '',
+    value: '',
+    type: 'text',
+    secret: false,
+    enabled: true
+  };
+  formik.setValues([...formik.values, newVariable]);
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c11266a and 9e65f18.

📒 Files selected for processing (40)
  • packages/bruno-app/src/components/CollectionSettings/Overview/Info/index.js (2 hunks)
  • packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (1 hunks)
  • packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/index.js (1 hunks)
  • packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/index.js (1 hunks)
  • packages/bruno-app/src/components/EnvironmentSettingsTab/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/EnvironmentSettingsTab/index.js (1 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSelector/index.js (6 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (1 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js (2 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js (1 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/StyledWrapper.js (0 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js (0 hunks)
  • packages/bruno-app/src/components/GlobalEnvironmentSettingsTab/index.js (1 hunks)
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/CopyEnvironment/index.js (0 hunks)
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/CreateEnvironment/index.js (0 hunks)
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/DeleteEnvironment/StyledWrapper.js (0 hunks)
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/DeleteEnvironment/index.js (0 hunks)
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/ConfirmSwitchEnv.js (0 hunks)
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js (0 hunks)
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (0 hunks)
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js (0 hunks)
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/StyledWrapper.js (0 hunks)
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/index.js (0 hunks)
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/RenameEnvironment/index.js (0 hunks)
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/StyledWrapper.js (0 hunks)
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/index.js (0 hunks)
  • packages/bruno-app/src/components/RequestTabPanel/index.js (2 hunks)
  • packages/bruno-app/src/components/RequestTabs/RequestTab/ConfirmEnvironmentClose/index.js (1 hunks)
  • packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js (2 hunks)
  • packages/bruno-app/src/components/RequestTabs/RequestTab/index.js (5 hunks)
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/CreateEnvironment/index.js (2 hunks)
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (5 hunks)
  • packages/bruno-app/src/providers/Hotkeys/index.js (5 hunks)
  • packages/bruno-app/src/providers/ReduxStore/slices/app.js (0 hunks)
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js (2 hunks)
  • packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js (3 hunks)
  • packages/bruno-app/src/providers/ReduxStore/slices/tabs.js (1 hunks)
💤 Files with no reviewable changes (16)
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/StyledWrapper.js
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/ConfirmSwitchEnv.js
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/DeleteEnvironment/index.js
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/StyledWrapper.js
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/DeleteEnvironment/StyledWrapper.js
  • packages/bruno-app/src/providers/ReduxStore/slices/app.js
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/index.js
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/CopyEnvironment/index.js
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/CreateEnvironment/index.js
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/RenameEnvironment/index.js
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/index.js
  • packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/StyledWrapper.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

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

Files:

  • packages/bruno-app/src/components/EnvironmentSettingsTab/StyledWrapper.js
  • packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/StyledWrapper.js
  • packages/bruno-app/src/providers/ReduxStore/slices/tabs.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
  • packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/index.js
  • packages/bruno-app/src/components/GlobalEnvironmentSettingsTab/index.js
  • packages/bruno-app/src/components/RequestTabs/RequestTab/ConfirmEnvironmentClose/index.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
  • packages/bruno-app/src/components/EnvironmentSettingsTab/index.js
  • packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/CreateEnvironment/index.js
  • packages/bruno-app/src/components/Environments/EnvironmentSelector/index.js
  • packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/StyledWrapper.js
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
  • packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js
  • packages/bruno-app/src/providers/Hotkeys/index.js
  • packages/bruno-app/src/components/RequestTabs/RequestTab/index.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js
  • packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/index.js
  • packages/bruno-app/src/components/CollectionSettings/Overview/Info/index.js
  • packages/bruno-app/src/components/RequestTabPanel/index.js
  • packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js
  • packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js
🧠 Learnings (3)
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{jsx,tsx} : Styled Components are used as wrappers to define both self and children components style; Tailwind classes are used specifically for layout based styles

Applied to files:

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

Applied to files:

  • packages/bruno-app/src/components/EnvironmentSettingsTab/StyledWrapper.js
  • packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/StyledWrapper.js
  • packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/StyledWrapper.js
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
  • packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{jsx,tsx} : Styled Component CSS might also change layout but Tailwind classes shouldn't define colors

Applied to files:

  • packages/bruno-app/src/components/EnvironmentSettingsTab/StyledWrapper.js
  • packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/StyledWrapper.js
  • packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/StyledWrapper.js
  • packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js
🧬 Code graph analysis (9)
packages/bruno-app/src/components/EnvironmentSettingsTab/StyledWrapper.js (2)
packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/StyledWrapper.js (1)
  • StyledWrapper (3-131)
packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/StyledWrapper.js (1)
  • StyledWrapper (3-277)
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (2)
packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (1)
  • EnvironmentVariables (18-395)
packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (1)
  • EnvironmentVariables (21-395)
packages/bruno-app/src/components/RequestTabs/RequestTab/ConfirmEnvironmentClose/index.js (2)
packages/bruno-app/src/components/Modal/index.js (1)
  • Modal (60-163)
packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js (1)
  • isGlobal (18-18)
packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js (1)
packages/bruno-app/src/utils/collections/index.js (7)
  • collection (582-582)
  • collection (1160-1160)
  • collection (1162-1162)
  • collection (1590-1590)
  • collection (1600-1600)
  • findCollectionByUid (80-82)
  • findCollectionByUid (80-82)
packages/bruno-app/src/components/EnvironmentSettingsTab/index.js (2)
packages/bruno-app/src/components/Environments/EnvironmentSelector/index.js (1)
  • environments (32-32)
packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/index.js (1)
  • EnvironmentList (15-440)
packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/StyledWrapper.js (2)
packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/StyledWrapper.js (1)
  • StyledWrapper (3-277)
packages/bruno-app/src/components/EnvironmentSettingsTab/StyledWrapper.js (1)
  • StyledWrapper (3-50)
packages/bruno-app/src/providers/Hotkeys/index.js (3)
packages/bruno-app/src/providers/ReduxStore/slices/tabs.js (3)
  • activeTab (151-151)
  • activeTab (158-158)
  • tabs (197-197)
packages/bruno-app/src/components/GlobalSearchModal/index.js (3)
  • tabs (31-31)
  • collections (30-30)
  • dispatch (28-28)
packages/bruno-app/src/components/RequestTabs/RequestTab/index.js (1)
  • dispatch (30-30)
packages/bruno-app/src/components/RequestTabs/RequestTab/index.js (3)
packages/bruno-app/src/components/RequestTabs/RequestTab/ConfirmEnvironmentClose/index.js (1)
  • ConfirmEnvironmentClose (5-41)
packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js (2)
  • saveEnvironment (1739-1777)
  • saveEnvironment (1739-1777)
packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js (2)
  • saveGlobalEnvironment (186-220)
  • saveGlobalEnvironment (186-220)
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js (1)
packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/index.js (1)
  • EnvironmentList (15-440)
🪛 Biome (2.1.2)
packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js

[error] 63-63: Do not add then to an object.

(lint/suspicious/noThenProperty)

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

1-52: LGTM!

Clean styled-component wrapper that correctly uses theme props for all color definitions. The structure with .empty-state and .shared-button classes is well-organized and follows project conventions.

packages/bruno-app/src/components/CollectionSettings/Overview/Info/index.js (2)

56-62: LGTM!

Clean migration to tab-based navigation. The tab UID pattern ${collection.uid}-environment-settings is consistent and the dispatch structure is correct.


70-78: LGTM!

Consistent pattern for global environment settings tab creation.

packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (1)

21-21: LGTM!

Removal of onClose prop aligns with the tab-based navigation pattern. The component no longer needs modal-closing semantics.

packages/bruno-app/src/components/RequestTabs/RequestTab/ConfirmEnvironmentClose/index.js (1)

1-43: LGTM!

Well-structured confirmation dialog with appropriate modal configuration (escape/outside-click disabled for data safety). Button layout follows standard UX patterns with destructive action isolated on the left.

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

74-94: LGTM!

Clean refactor of handleSettingsClick from modal-based to tab-based navigation. The conditional logic correctly handles both collection and global environment settings.

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

81-98: No issues found. The indentBorder theme value already contains the complete border definition.

The indentBorder property in both light and dark themes is defined as a full border string (e.g., 'solid 1px #f9f9f9'), not just a color value. Line 86 correctly uses it without adding redundant prefixes:

border: ${(props) => props.theme.sidebar.collection.item.indentBorder};

This renders as expected since the theme value already includes width, style, and color.

Likely an incorrect or invalid review comment.

packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/CreateEnvironment/index.js (1)

35-41: Copy updates correctly reflect global environment scope

The duplicate-name error, success toast, and modal title now consistently talk about “Global environment”, which matches the use of the globalEnvironments slice; behavior is unchanged and looks good.

Also applies to: 65-67

packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js (1)

3-4: Environment tab icons/labels integrate cleanly with SpecialTab

The added cases for environment-settings and global-environment-settings mirror the existing pattern for other special tabs, and the choice of IconDatabase / IconWorld with “Environments” / “Global Environments” labels fits the new tab types without changing any external API.

Also applies to: 56-71

packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js (1)

9-13: Confirm there are no remaining callers relying on onClose from EnvironmentDetails/Variables

EnvironmentDetails and its nested EnvironmentVariables no longer receive or propagate an onClose prop, which means inner components can’t directly close the environment settings view anymore. That’s consistent with the tab-based model where navigation/closing is handled at the tab level, but if any legacy modal-based usages of this component remain, they may now fail to close as expected.

It’s worth double-checking all imports/usages of this EnvironmentDetails variant to ensure nothing still depends on an onClose-driven close path.

Also applies to: 53-55

packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (3)

101-122: Potential stale closure issue in draft restoration effect.

The effect references formik.setValues but formik isn't in the dependency array. While this typically works due to formik's stable reference, omitting it from dependencies can lead to stale closures if the form instance changes. Consider adding formik.setValues to the dependency array or use a ref to track the setter.

Also, globalEnvironmentsDraft?.variables in the dependency array will cause the effect to re-run on every variable change, potentially causing unwanted re-renders when editing.


240-260: LGTM on save flow.

The save handler properly validates before dispatching, clones data to avoid mutation, and handles success/error states with appropriate toasts.


13-17: No action required—the component is correctly using global environment Redux actions.

The file path indicates the component hierarchy for workspace environment management, not collection-scoped environments. The implementation confirms this: it imports and dispatches saveGlobalEnvironment, setGlobalEnvironmentsDraft, and clearGlobalEnvironmentsDraft consistently, and selects from state.globalEnvironments. This is the correct pattern for global environment variables.

A separate EnvironmentVariables component exists at packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js for collection-scoped environments.

Likely an incorrect or invalid review comment.

packages/bruno-app/src/components/RequestTabs/RequestTab/index.js (3)

4-7: LGTM on imports.

Clean separation of collection and global environment actions.


157-159: Draft detection relies on Redux state correctly.

The hasEnvironmentDraft and hasGlobalEnvironmentDraft variables correctly check both tab type and draft presence. The useSelector call for globalEnvironmentsDraft is appropriately placed.


302-305: LGTM on SpecialTab rendering.

The new environment tab types are correctly integrated into the conditional rendering chain with appropriate close handlers and draft indicators.

packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js (2)

697-709: LGTM on new draft reducers.

The setEnvironmentsDraft and clearEnvironmentsDraft reducers follow the established pattern for managing draft state in collections. The shape { environmentUid, variables } mirrors the global environments draft structure, ensuring consistency.


3388-3389: LGTM on exports.

Actions correctly exported for use in components.

packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js (3)

6-10: LGTM on initial state update.

Adding globalEnvironmentsDraft: null to the initial state properly initializes the draft field.


77-84: LGTM on draft reducers.

Clean implementation following Redux Toolkit patterns. The draft shape { environmentUid, variables } is consistent with the collections slice implementation.


95-97: LGTM on exports.

New actions correctly exported alongside existing ones.

packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/StyledWrapper.js (1)

1-133: LGTM on styled component.

The StyledWrapper follows project conventions:

  • All colors derived from props.theme (no hardcoded values)
  • Flex-based layout with Tailwind-compatible structure
  • Consistent with peer StyledWrapper.js files in EnvironmentSettingsTab and EnvironmentList
  • Interactive states (hover, focus) appropriately themed

Based on learnings, the theme prop usage for colors is correct.

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

115-121: Tab creation for environment settings looks good.

The hotkey now correctly dispatches addTab to open environment settings in a tab instead of a modal, aligning with the new tab-based navigation architecture.

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

46-57: Component structure looks good.

The EnvironmentList integration with props for selection, modification state, and export modal control follows a clean pattern.


58-64: Align prop names between modal components for consistency.

ImportEnvironmentModal uses type prop while ExportEnvironmentModal uses environmentType prop. Both should use the same name to maintain consistency across related components.

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

326-389: Environment list rendering and inline editing implementation is solid.

The filtering, selection, renaming flow, and activation logic are well-structured with proper event handling (stopPropagation, preventDefault on mouseDown for buttons).

packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/index.js (1)

114-181: Component structure and modal integration look good.

The inline rename UI with save/cancel buttons, keyboard handling (Enter/Escape), and modal triggers for copy/delete are well-implemented.

packages/bruno-app/src/components/EnvironmentSettingsTab/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (2)

61-72: Static analysis false positive - Yup schema is valid.

The Biome warning about noThenProperty is a false positive. This is Yup's conditional schema API (.when().then()), not adding a then property to a plain object.


284-393: Table rendering and row handling implementation is well-structured.

The conditional rendering for last empty row, checkbox visibility, read-only value handling with tooltip, and action buttons are cleanly implemented.

Comment on lines +251 to +266
onSaveAndClose={() => {
const draft = collection.environmentsDraft;
if (draft?.environmentUid && draft?.variables) {
dispatch(saveEnvironment(draft.variables, draft.environmentUid, collection.uid))
.then(() => {
dispatch(clearEnvironmentsDraft({ collectionUid: collection.uid }));
dispatch(closeTabs({ tabUids: [tab.uid] }));
setShowConfirmEnvironmentClose(false);
toast.success('Environment saved');
})
.catch((err) => {
console.log('err', err);
toast.error('Failed to save environment');
});
}
}}
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 null check in save handler could silently fail.

The onSaveAndClose callback only executes the save if draft?.environmentUid && draft?.variables are truthy. If either is falsy, the modal remains open with no feedback to the user.

Consider showing an error toast or closing without save when draft is incomplete.

 onSaveAndClose={() => {
   const draft = collection.environmentsDraft;
   if (draft?.environmentUid && draft?.variables) {
     dispatch(saveEnvironment(draft.variables, draft.environmentUid, collection.uid))
       .then(() => {
         dispatch(clearEnvironmentsDraft({ collectionUid: collection.uid }));
         dispatch(closeTabs({ tabUids: [tab.uid] }));
         setShowConfirmEnvironmentClose(false);
         toast.success('Environment saved');
       })
       .catch((err) => {
         console.log('err', err);
         toast.error('Failed to save environment');
       });
+  } else {
+    toast.error('No draft to save');
+    setShowConfirmEnvironmentClose(false);
   }
 }}
📝 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
onSaveAndClose={() => {
const draft = collection.environmentsDraft;
if (draft?.environmentUid && draft?.variables) {
dispatch(saveEnvironment(draft.variables, draft.environmentUid, collection.uid))
.then(() => {
dispatch(clearEnvironmentsDraft({ collectionUid: collection.uid }));
dispatch(closeTabs({ tabUids: [tab.uid] }));
setShowConfirmEnvironmentClose(false);
toast.success('Environment saved');
})
.catch((err) => {
console.log('err', err);
toast.error('Failed to save environment');
});
}
}}
onSaveAndClose={() => {
const draft = collection.environmentsDraft;
if (draft?.environmentUid && draft?.variables) {
dispatch(saveEnvironment(draft.variables, draft.environmentUid, collection.uid))
.then(() => {
dispatch(clearEnvironmentsDraft({ collectionUid: collection.uid }));
dispatch(closeTabs({ tabUids: [tab.uid] }));
setShowConfirmEnvironmentClose(false);
toast.success('Environment saved');
})
.catch((err) => {
console.log('err', err);
toast.error('Failed to save environment');
});
} else {
toast.error('No draft to save');
setShowConfirmEnvironmentClose(false);
}
}}
🤖 Prompt for AI Agents
In packages/bruno-app/src/components/RequestTabs/RequestTab/index.js around
lines 251-266, the onSaveAndClose handler only runs when draft?.environmentUid
&& draft?.variables are truthy, leaving the modal open with no feedback if the
draft is incomplete; add an else branch that handles the missing draft case by
calling toast.error with a clear message (e.g., "Incomplete environment draft —
cannot save"), optionally log the missing fields for debugging, and keep the
modal open (do not proceed to closeTabs) so the user can correct the draft;
ensure this branch does not attempt to dispatch saveEnvironment.

Comment on lines +278 to +294
onSaveAndClose={() => {
const draft = globalEnvironmentsDraft;
if (draft?.environmentUid && draft?.variables) {
dispatch(saveGlobalEnvironment({ variables: draft.variables, environmentUid: draft.environmentUid }))
.then(() => {
dispatch(clearGlobalEnvironmentsDraft());
dispatch(closeTabs({ tabUids: [tab.uid] }));
setShowConfirmGlobalEnvironmentClose(false);
toast.success('Global environment saved');
})
.catch((err) => {
console.log('err', err);
toast.error('Failed to save global environment');
});
}
}}
/>
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

Same issue: missing fallback for incomplete global environment draft.

Mirror the same handling pattern as collection environments for consistency.

🤖 Prompt for AI Agents
In packages/bruno-app/src/components/RequestTabs/RequestTab/index.js around
lines 278-294, the onSaveAndClose handler attempts to save only when draft has
environmentUid and variables but lacks a fallback for incomplete drafts; add an
else branch that mirrors the collection-environment handling: when draft is
missing required fields, avoid calling save, dispatch
clearGlobalEnvironmentsDraft(), dispatch closeTabs({ tabUids: [tab.uid] }),
ensure setShowConfirmGlobalEnvironmentClose is set to the proper state (hide
confirmation), and show a user-facing toast (e.g., info or warning) indicating
there was nothing to save or the draft was incomplete.

@naman-bruno naman-bruno marked this pull request as draft December 15, 2025 11:39
@naman-bruno naman-bruno marked this pull request as ready for review December 17, 2025 11:02
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

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

205-211: Verify collection prop usage in GlobalEnvironmentSettings.

Past review flagged that GlobalEnvironmentSettings may not use the collection prop being passed at line 210. If the component doesn't accept or use this prop, it should be removed to maintain API consistency.

Run the following script to verify prop usage:

#!/bin/bash
# Check if GlobalEnvironmentSettings component accepts collection prop

# Check component definition
cat packages/bruno-app/src/components/Environments/GlobalEnvironmentSettings/index.js | head -50

# Search for collection prop usage in the component
rg -n "collection" packages/bruno-app/src/components/Environments/GlobalEnvironmentSettings/ -C 3
🧹 Nitpick comments (21)
tests/request/newlines/newlines-persistence.spec.ts (1)

5-78: Consider using test.step for better report readability.

The test could benefit from wrapping logical sections in test.step calls (e.g., "Create collection and accept sandbox", "Create request and add data", "Verify persistence after restart"). This makes Playwright reports easier to navigate and understand.

Example structure:

 test('should persist request with newlines across app restarts', async ({ createTmpDir, launchElectronApp }) => {
   const userDataPath = await createTmpDir('newlines-persistence-userdata');
   const collectionPath = await createTmpDir('newlines-persistence-collection');

-  // Create collection and request
-  const app1 = await launchElectronApp({ userDataPath });
-  const page = await app1.firstWindow();
-
-  await page.getByTestId('collections-header-add-menu').click();
-  ...
+  await test.step('Create collection and accept sandbox', async () => {
+    const app1 = await launchElectronApp({ userDataPath });
+    const page = await app1.firstWindow();
+    await page.getByTestId('collections-header-add-menu').click();
+    ...
+    await openCollectionAndAcceptSandbox(page, 'newlines-persistence', 'safe');
+  });
+
+  await test.step('Create request and add data with newlines', async () => {
+    ...
+  });
+
+  await test.step('Verify persistence after restart', async () => {
+    ...
+  });

As per coding guidelines, test.step usage is promoted for improved report readability.

tests/global-environments/non-string-values.spec.ts (3)

57-58: Consider more robust selectors for environment variable inputs.

Selecting inputs by value attribute (e.g., input[value="numericVar"]) can be fragile if the value isn't consistently set or if the DOM structure changes. Consider using data-testid attributes or a combination of text content and structural selectors for better stability.

Also applies to: 112-112


26-27: Extract repeated envTab locator to reduce duplication.

The envTab locator is defined three times with identical logic. Consider extracting it to a const at the test function level to follow DRY principles.

Example:

// At the top of the test function
const getEnvTab = () => page.locator('.request-tab').filter({ hasText: 'Global Environments' });

Then use getEnvTab() instead of redefining the locator each time.

Also applies to: 53-54, 125-126


37-38: Tab close pattern is repeated.

The pattern of hovering over the tab and clicking the close icon appears twice. If this pattern is used across multiple tests, consider extracting it to a helper function in the utils module.

Also applies to: 126-127

packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (1)

175-210: handleRemoveVar dependency concern.

The useCallback depends on formik.values, which changes on every edit. This effectively disables memoization. Consider using a ref or restructuring to avoid the dependency.

Apply this change to improve memoization:

-  const handleRemoveVar = useCallback((id) => {
-    const currentValues = formik.values;
+  const handleRemoveVar = useCallback((id) => {
+    const currentValues = formik.values;

Or use formik.setValues with a function form to avoid the dependency:

   const handleRemoveVar = useCallback((id) => {
-    const currentValues = formik.values;
-
-    if (!currentValues || currentValues.length === 0) {
+    formik.setValues((currentValues) => {
+      if (!currentValues || currentValues.length === 0) {
-      return;
+        return currentValues;
-    }
+      }
-
-    const lastRow = currentValues[currentValues.length - 1];
-    const isLastEmptyRow = lastRow?.uid === id && (!lastRow.name || lastRow.name.trim() === '');
-
-    if (isLastEmptyRow) {
-      return;
-    }
-
-    const filteredValues = currentValues.filter((variable) => variable.uid !== id);
-
-    const hasEmptyLastRow = filteredValues.length > 0
-      && (!filteredValues[filteredValues.length - 1].name
-        || filteredValues[filteredValues.length - 1].name.trim() === '');
-
-    const newValues = hasEmptyLastRow
-      ? filteredValues
-      : [
-          ...filteredValues,
-          {
-            uid: uuid(),
-            name: '',
-            value: '',
-            type: 'text',
-            secret: false,
-            enabled: true
-          }
-        ];
-
-    formik.setValues(newValues);
-  }, [formik.values]);
+
+      const lastRow = currentValues[currentValues.length - 1];
+      const isLastEmptyRow = lastRow?.uid === id && (!lastRow.name || lastRow.name.trim() === '');
+
+      if (isLastEmptyRow) {
+        return currentValues;
+      }
+
+      const filteredValues = currentValues.filter((variable) => variable.uid !== id);
+
+      const hasEmptyLastRow = filteredValues.length > 0
+        && (!filteredValues[filteredValues.length - 1].name
+          || filteredValues[filteredValues.length - 1].name.trim() === '');
+
+      return hasEmptyLastRow
+        ? filteredValues
+        : [
+            ...filteredValues,
+            {
+              uid: uuid(),
+              name: '',
+              value: '',
+              type: 'text',
+              secret: false,
+              enabled: true
+            }
+          ];
+    });
+  }, []);
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (1)

178-214: handleRemoveVar dependency concern.

Similar to the global variant, the useCallback depends on formik.values, which defeats memoization. Consider using the function form of formik.setValues to eliminate this dependency.

Use the function form of setValues to avoid depending on formik.values:

   const handleRemoveVar = useCallback((id) => {
-    const currentValues = formik.values;
-
-    if (!currentValues || currentValues.length === 0) {
-      return;
-    }
-
-    const lastRow = currentValues[currentValues.length - 1];
-    const isLastEmptyRow = lastRow?.uid === id && (!lastRow.name || lastRow.name.trim() === '');
-
-    if (isLastEmptyRow) {
-      return;
-    }
-
-    const filteredValues = currentValues.filter((variable) => variable.uid !== id);
-
-    const hasEmptyLastRow
-      = filteredValues.length > 0
-        && (!filteredValues[filteredValues.length - 1].name
-          || filteredValues[filteredValues.length - 1].name.trim() === '');
-
-    const newValues = hasEmptyLastRow
-      ? filteredValues
-      : [
-          ...filteredValues,
-          {
-            uid: uuid(),
-            name: '',
-            value: '',
-            type: 'text',
-            secret: false,
-            enabled: true
-          }
-        ];
-
-    formik.setValues(newValues);
-  }, [formik.values]);
+    formik.setValues((currentValues) => {
+      if (!currentValues || currentValues.length === 0) {
+        return currentValues;
+      }
+
+      const lastRow = currentValues[currentValues.length - 1];
+      const isLastEmptyRow = lastRow?.uid === id && (!lastRow.name || lastRow.name.trim() === '');
+
+      if (isLastEmptyRow) {
+        return currentValues;
+      }
+
+      const filteredValues = currentValues.filter((variable) => variable.uid !== id);
+
+      const hasEmptyLastRow
+        = filteredValues.length > 0
+          && (!filteredValues[filteredValues.length - 1].name
+            || filteredValues[filteredValues.length - 1].name.trim() === '');
+
+      return hasEmptyLastRow
+        ? filteredValues
+        : [
+            ...filteredValues,
+            {
+              uid: uuid(),
+              name: '',
+              value: '',
+              type: 'text',
+              secret: false,
+              enabled: true
+            }
+          ];
+    });
+  }, []);
tests/environments/import-environment/collection-env-import.spec.ts (1)

55-66: Consider wrapping verification in test.step for better reporting.

The tab verification and assertions work correctly, but wrapping this logic in a test.step would improve test report readability and align with the Playwright best practices promoted in the coding guidelines.

Apply this pattern:

+    await test.step('Verify imported environment variables in tab', async () => {
       const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
       await expect(envTab).toBeVisible();
 
       await expect(page.locator('input[name="0.name"]')).toHaveValue('host');
       await expect(page.locator('input[name="1.name"]')).toHaveValue('userId');
       await expect(page.locator('input[name="2.name"]')).toHaveValue('apiKey');
       await expect(page.locator('input[name="3.name"]')).toHaveValue('postTitle');
       await expect(page.locator('input[name="4.name"]')).toHaveValue('postBody');
       await expect(page.locator('input[name="5.name"]')).toHaveValue('secretApiToken');
       await expect(page.locator('input[name="5.secret"]')).toBeChecked();
       await envTab.hover();
       await envTab.getByTestId('request-tab-close-icon').click();
+    });
tests/environments/import-environment/global-env-import.spec.ts (1)

50-61: Consider wrapping verification in test.step for better reporting.

Similar to the collection environment import test, wrapping this tab verification logic in a test.step would improve report readability and maintainability.

Apply this pattern:

+    await test.step('Verify imported global environment variables in tab', async () => {
       const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
       await expect(envTab).toBeVisible();
 
       await expect(page.locator('input[name="0.name"]')).toHaveValue('host');
       await expect(page.locator('input[name="1.name"]')).toHaveValue('userId');
       await expect(page.locator('input[name="2.name"]')).toHaveValue('apiKey');
       await expect(page.locator('input[name="3.name"]')).toHaveValue('postTitle');
       await expect(page.locator('input[name="4.name"]')).toHaveValue('postBody');
       await expect(page.locator('input[name="5.name"]')).toHaveValue('secretApiToken');
       await expect(page.locator('input[name="5.secret"]')).toBeChecked();
       await envTab.hover();
       await envTab.getByTestId('request-tab-close-icon').click();
+    });
tests/environments/api-setEnvVar/api-setEnvVar-with-persist.spec.ts (1)

31-38: Consider extracting tab verification into a helper function.

The tab verification and close pattern appears twice in this test (lines 31-38 and 51-59). Extracting this into a reusable helper would reduce duplication and improve maintainability, especially since this pattern is also used across multiple test files.

Example helper:

async function verifyAndCloseEnvironmentsTab(page, expectedRows: string[]) {
  const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
  await expect(envTab).toBeVisible();
  
  for (const rowName of expectedRows) {
    await expect(page.getByRole('row', { name: rowName })).toBeVisible();
  }
  
  await envTab.hover();
  await envTab.getByTestId('request-tab-close-icon').click();
}
tests/environments/global-env-config-selection/global-env-config-selection.spec.ts (1)

24-31: Consider wrapping verification in test.step for better reporting.

The tab verification logic would benefit from a test.step wrapper to improve test report readability, consistent with Playwright best practices.

+    await test.step('Verify global environment tab shows active environment', async () => {
       const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
       await expect(envTab).toBeVisible();
 
       const activeEnvItem = page.locator('.environment-item.active');
       await expect(activeEnvItem).toContainText(currentEnvName);
 
       await envTab.hover();
       await envTab.getByTestId('request-tab-close-icon').click();
+    });
tests/environments/api-setEnvVar/api-setEnvVar-without-persist.spec.ts (1)

24-31: Consider extracting repeated verification pattern.

Similar to the persist test, this verification pattern is duplicated (lines 24-31 and 45-51). Consider extracting into a shared helper function to reduce duplication across test files.

Additionally, wrapping in test.step would improve report readability:

+    await test.step('Verify environment variables are set', async () => {
       const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
       await expect(envTab).toBeVisible();
 
       await expect(page.getByRole('row', { name: 'token' }).getByRole('cell').nth(1)).toBeVisible();
       await expect(page.getByRole('row', { name: 'secret' }).getByRole('cell').nth(2)).toBeVisible();
       await envTab.hover();
       await envTab.getByTestId('request-tab-close-icon').click();
+    });
tests/environments/api-setEnvVar/multiple-persist-vars.spec.ts (2)

13-15: Reduce waitForTimeout usage.

The test uses waitForTimeout(200) multiple times, which the coding guidelines discourage. Consider replacing these with explicit locator waits or expectations.

Replace:

-    await page.waitForTimeout(200);
-    await page.locator('#configure-env').click();
-    await page.waitForTimeout(200);
+    await page.locator('#configure-env').click();
+    const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+    await expect(envTab).toBeVisible();

Based on coding guidelines: Try to reduce usage of page.waitForTimeout(); unless absolutely necessary.


76-77: Consider replacing waitForTimeout with locator-based waits.

Similar to the cleanup section, these timeouts could be replaced with explicit visibility checks for more reliable test execution.

-    await page.waitForTimeout(200);
-    await page.locator('#configure-env').click();
-    await page.waitForTimeout(200);
+    await page.locator('#configure-env').click();
+    const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+    await expect(envTab).toBeVisible();
tests/environments/import-environment/bruno-env-import/collection-env-import/collection-env-import.spec.ts (1)

99-99: Replace waitForTimeout with explicit wait.

Instead of a fixed timeout, consider waiting for a specific UI element to be ready.

-    await test.step('Verify both environments are available in selector', async () => {
-      await page.waitForTimeout(500);
-      await page.getByTestId('environment-selector-trigger').click();
+    await test.step('Verify both environments are available in selector', async () => {
+      await page.getByTestId('environment-selector-trigger').click();
+      await expect(page.locator('.dropdown-item').filter({ hasText: /^local$/ })).toBeVisible({ timeout: 10000 });

Based on coding guidelines.

tests/environments/update-global-environment-via-script/global-env-update-via-script.spec.ts (1)

35-56: Consider extracting repeated row locator pattern.

The pattern of locating a row by input value and reading CodeMirror content is repeated four times. Extracting this to a helper function would improve maintainability.

Example helper:

const getEnvVariableValue = async (page: Page, variableName: string): Promise<string> => {
  const row = page.locator('tbody tr').filter({ has: page.locator(`input[value="${variableName}"]`) });
  return await row.locator('.CodeMirror-line').first().textContent();
};

Then use:

const value = await getEnvVariableValue(page, 'existingEnvEnabled');
await expect(value).toContain('newExistingEnvEnabledValue');
tests/environments/export-environment/collection-env-export/collection-env-export.spec.ts (2)

45-45: Consider centralizing the export button selector.

The selector button[title="Export environment"] appears multiple times. Consider defining it once at the top of the test suite or in a locator helper.

const EXPORT_BUTTON = 'button[title="Export environment"]';
// Then use:
await page.locator(EXPORT_BUTTON).click();

This improves maintainability if the selector changes.

Also applies to: 102-102, 170-170, 227-227, 282-282, 337-337, 396-396


39-40: Repeated envTab locator pattern.

The pattern of defining and verifying the Environments tab is repeated across multiple tests. Consider extracting to a helper function in the test utilities.

Example:

const verifyEnvironmentsTab = async (page: Page, type: 'collection' | 'global' = 'collection') => {
  const tabLabel = type === 'collection' ? 'Environments' : 'Global Environments';
  const envTab = page.locator('.request-tab').filter({ hasText: tabLabel });
  await expect(envTab).toBeVisible();
  return envTab;
};

Also applies to: 96-97, 164-165, 222-223, 277-278, 332-333, 390-391

tests/environments/export-environment/global-env-export/global-env-export.spec.ts (1)

300-300: Remove unnecessary waitForTimeout.

The waitForTimeout(200) appears unnecessary here since the success message visibility check immediately follows.

     await test.step('Execute export and verify success', async () => {
       // Export the environments
       await page.getByRole('button', { name: 'Export 2 Environments' }).click();
-      await page.waitForTimeout(200);
       // Verify success message
       await expect(page.getByText('Environment(s) exported successfully', { exact: false }).first()).toBeVisible();
     });

The expect().toBeVisible() already includes an implicit wait.

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

30-30: Optional: Simplify with default parameter.

The environments extraction could be simplified using a default parameter.

-const EnvironmentSettings = ({ collection }) => {
+const EnvironmentSettings = ({ collection = {} }) => {
   const [isModified, setIsModified] = useState(false);
   const [selectedEnvironment, setSelectedEnvironment] = useState(null);
   const [tab, setTab] = useState('default');
   const [showExportModal, setShowExportModal] = useState(false);

-  const environments = collection?.environments || [];
+  const environments = collection.environments || [];
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js (2)

23-49: Minor: Redundant length validation.

Lines 28-30 check if (name.length < 1) which is redundant since lines 24-26 already check if (!name || name.trim() === ''). An empty string will have length 0, so this condition will never be reached independently.

   if (!name || name.trim() === '') {
     return 'Name is required';
   }

-  if (name.length < 1) {
-    return 'Must be at least 1 character';
-  }
-
   if (name.length > 255) {
     return 'Must be 255 characters or less';
   }

51-59: Consider adding a comment for the setTimeout delay.

The 50ms setTimeout is used to ensure the input is rendered before focusing. A brief comment would clarify this timing for future maintainers.

   const handleRenameClick = () => {
     setIsRenaming(true);
     setNewName(environment.name);
     setNameError('');
+    // Wait for input to render before focusing
     setTimeout(() => {
       inputRef.current?.focus();
       inputRef.current?.select();
     }, 50);
   };
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9e65f18 and 48341fa.

📒 Files selected for processing (40)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/ConfirmSwitchEnv.js (0 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/constants.js (0 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (2 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js (1 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js (6 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/ManageSecrets/index.js (0 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/RenameEnvironment/index.js (0 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js (1 hunks)
  • packages/bruno-app/src/components/Environments/GlobalEnvironmentSettings/index.js (1 hunks)
  • packages/bruno-app/src/components/RequestTabPanel/index.js (2 hunks)
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (9 hunks)
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/WorkspaceHome/index.js (1 hunks)
  • packages/bruno-app/src/providers/Hotkeys/index.js (5 hunks)
  • packages/bruno-app/src/utils/common/regex.js (1 hunks)
  • tests/environments/api-setEnvVar/api-setEnvVar-with-persist.spec.ts (2 hunks)
  • tests/environments/api-setEnvVar/api-setEnvVar-without-persist.spec.ts (2 hunks)
  • tests/environments/api-setEnvVar/multiple-persist-vars.spec.ts (2 hunks)
  • tests/environments/collection-env-config-selection/collection-env-config-selection.spec.ts (1 hunks)
  • tests/environments/create-environment/collection-env-create.spec.ts (0 hunks)
  • tests/environments/create-environment/global-env-create.spec.ts (0 hunks)
  • tests/environments/export-environment/collection-env-export/collection-env-export.spec.ts (7 hunks)
  • tests/environments/export-environment/global-env-export/global-env-export.spec.ts (7 hunks)
  • tests/environments/global-env-config-selection/global-env-config-selection.spec.ts (1 hunks)
  • tests/environments/import-environment/bruno-env-import/collection-env-import/collection-env-import.spec.ts (3 hunks)
  • tests/environments/import-environment/bruno-env-import/global-env-import/global-env-import.spec.ts (5 hunks)
  • tests/environments/import-environment/collection-env-import.spec.ts (1 hunks)
  • tests/environments/import-environment/global-env-import.spec.ts (1 hunks)
  • tests/environments/multiline-variables/write-multiline-variable.spec.ts (3 hunks)
  • tests/environments/update-global-environment-via-script/global-env-update-via-script.spec.ts (1 hunks)
  • tests/global-environments/non-string-values.spec.ts (7 hunks)
  • tests/import/insomnia/import-insomnia-v4-environments.spec.ts (1 hunks)
  • tests/import/insomnia/import-insomnia-v5-environments.spec.ts (1 hunks)
  • tests/request/newlines/newlines-persistence.spec.ts (1 hunks)
  • tests/utils/page/actions.ts (2 hunks)
  • tests/utils/page/locators.ts (1 hunks)
💤 Files with no reviewable changes (6)
  • tests/environments/create-environment/global-env-create.spec.ts
  • tests/environments/create-environment/collection-env-create.spec.ts
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/constants.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/RenameEnvironment/index.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/ConfirmSwitchEnv.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/ManageSecrets/index.js
✅ Files skipped from review due to trivial changes (2)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js
  • packages/bruno-app/src/utils/common/regex.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/bruno-app/src/providers/Hotkeys/index.js
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

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

Files:

  • tests/environments/import-environment/collection-env-import.spec.ts
  • tests/environments/global-env-config-selection/global-env-config-selection.spec.ts
  • tests/environments/api-setEnvVar/multiple-persist-vars.spec.ts
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/StyledWrapper.js
  • tests/request/newlines/newlines-persistence.spec.ts
  • tests/import/insomnia/import-insomnia-v5-environments.spec.ts
  • tests/environments/collection-env-config-selection/collection-env-config-selection.spec.ts
  • tests/environments/import-environment/global-env-import.spec.ts
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/StyledWrapper.js
  • packages/bruno-app/src/components/WorkspaceHome/index.js
  • tests/import/insomnia/import-insomnia-v4-environments.spec.ts
  • packages/bruno-app/src/components/Environments/GlobalEnvironmentSettings/index.js
  • tests/environments/api-setEnvVar/api-setEnvVar-with-persist.spec.ts
  • tests/environments/import-environment/bruno-env-import/global-env-import/global-env-import.spec.ts
  • tests/environments/api-setEnvVar/api-setEnvVar-without-persist.spec.ts
  • tests/environments/import-environment/bruno-env-import/collection-env-import/collection-env-import.spec.ts
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js
  • tests/environments/export-environment/collection-env-export/collection-env-export.spec.ts
  • packages/bruno-app/src/components/RequestTabPanel/index.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js
  • tests/utils/page/locators.ts
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/StyledWrapper.js
  • tests/utils/page/actions.ts
  • tests/global-environments/non-string-values.spec.ts
  • tests/environments/update-global-environment-via-script/global-env-update-via-script.spec.ts
  • tests/environments/multiline-variables/write-multiline-variable.spec.ts
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
  • tests/environments/export-environment/global-env-export/global-env-export.spec.ts
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js
tests/**/**.*

⚙️ CodeRabbit configuration file

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

  • Follow best practices for Playwright code and e2e automation

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

  • Avoid using page.pause() in code

  • Use locator variables for locators

  • Avoid using test.only

  • Use multiple assertions

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

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

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

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

Files:

  • tests/environments/import-environment/collection-env-import.spec.ts
  • tests/environments/global-env-config-selection/global-env-config-selection.spec.ts
  • tests/environments/api-setEnvVar/multiple-persist-vars.spec.ts
  • tests/request/newlines/newlines-persistence.spec.ts
  • tests/import/insomnia/import-insomnia-v5-environments.spec.ts
  • tests/environments/collection-env-config-selection/collection-env-config-selection.spec.ts
  • tests/environments/import-environment/global-env-import.spec.ts
  • tests/import/insomnia/import-insomnia-v4-environments.spec.ts
  • tests/environments/api-setEnvVar/api-setEnvVar-with-persist.spec.ts
  • tests/environments/import-environment/bruno-env-import/global-env-import/global-env-import.spec.ts
  • tests/environments/api-setEnvVar/api-setEnvVar-without-persist.spec.ts
  • tests/environments/import-environment/bruno-env-import/collection-env-import/collection-env-import.spec.ts
  • tests/environments/export-environment/collection-env-export/collection-env-export.spec.ts
  • tests/utils/page/locators.ts
  • tests/utils/page/actions.ts
  • tests/global-environments/non-string-values.spec.ts
  • tests/environments/update-global-environment-via-script/global-env-update-via-script.spec.ts
  • tests/environments/multiline-variables/write-multiline-variable.spec.ts
  • tests/environments/export-environment/global-env-export/global-env-export.spec.ts
🧠 Learnings (7)
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{jsx,tsx} : Styled Components are used as wrappers to define both self and children components style; Tailwind classes are used specifically for layout based styles

Applied to files:

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

Applied to files:

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

Applied to files:

  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/StyledWrapper.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/StyledWrapper.js
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js
📚 Learning: 2025-12-16T07:16:08.934Z
Learnt from: sanish-bruno
Repo: usebruno/bruno PR: 6090
File: tests/scripting/hooks/init-user-data/ui-state-snapshot.json:1-8
Timestamp: 2025-12-16T07:16:08.934Z
Learning: For e2e tests in the bruno repository: Collections that are shared between CLI and UI tests (comprehensive test suites testing core functionality) should be placed in `packages/bruno-tests/` to avoid duplication. The `tests/**/fixtures/collection` pattern should be used for test-specific collections that test particular UI behaviors or are specific to a single test file.

Applied to files:

  • tests/request/newlines/newlines-persistence.spec.ts
  • tests/environments/import-environment/bruno-env-import/collection-env-import/collection-env-import.spec.ts
📚 Learning: 2025-12-02T20:48:05.670Z
Learnt from: NikHillAgar
Repo: usebruno/bruno PR: 6279
File: packages/bruno-app/src/components/RequestPane/PromptVariables/PromptVariablesModal/index.js:79-86
Timestamp: 2025-12-02T20:48:05.670Z
Learning: In React 19+, ref callbacks support returning cleanup functions. When a ref callback returns a function, React will call it when the element unmounts or the ref changes, similar to useEffect cleanup. This is documented at https://react.dev/learn/manipulating-the-dom-with-refs#how-to-manage-a-list-of-refs-using-a-ref-callback

Applied to files:

  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Use consistent patterns and helper utilities where they improve clarity. Prefer shared test utilities over copy-pasted setup code, but only when it actually reduces complexity

Applied to files:

  • tests/global-environments/non-string-values.spec.ts
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.test.{js,jsx,ts,tsx} : Add tests for any new functionality or meaningful changes. If code is added, removed, or significantly modified, corresponding tests should be updated or created

Applied to files:

  • tests/environments/update-global-environment-via-script/global-env-update-via-script.spec.ts
🧬 Code graph analysis (4)
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/StyledWrapper.js (2)
packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/StyledWrapper.js (1)
  • StyledWrapper (3-131)
packages/bruno-app/src/components/Environments/EnvironmentSettings/StyledWrapper.js (1)
  • StyledWrapper (3-50)
packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (3)
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (9)
  • EnvironmentVariables (18-439)
  • useTheme (20-20)
  • useSelector (21-21)
  • hasDraftForThisEnv (24-24)
  • handleRemoveVar (178-214)
  • handleNameBlur (235-237)
  • handleNameKeyDown (239-244)
  • handleSave (246-292)
  • handleSaveRef (311-311)
packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/index.js (2)
  • handleNameBlur (92-101)
  • handleNameKeyDown (103-111)
packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js (3)
  • environment (33-33)
  • environment (42-42)
  • environment (61-61)
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js (5)
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js (2)
  • dispatch (25-25)
  • inputRef (34-34)
packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/CreateEnvironment/index.js (2)
  • dispatch (19-19)
  • inputRef (20-20)
packages/bruno-electron/src/utils/filesystem.js (1)
  • validateName (229-244)
packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js (2)
  • renameEnvironment (1689-1714)
  • renameEnvironment (1689-1714)
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/StyledWrapper.js (1)
  • StyledWrapper (3-132)
tests/utils/page/actions.ts (2)
packages/bruno-electron/src/app/workspace-watcher.js (1)
  • type (41-41)
packages/bruno-electron/src/utils/workspace-config.js (1)
  • type (171-171)
🪛 Biome (2.1.2)
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js

[error] 63-63: Do not add then to an object.

(lint/suspicious/noThenProperty)

🔇 Additional comments (52)
tests/request/newlines/newlines-persistence.spec.ts (1)

19-20: Good consolidation of sandbox acceptance flow.

Moving the sandbox acceptance to immediately after collection creation and removing the duplicate call (per the AI summary) is cleaner. This aligns well with the tab-based UI changes in the PR.

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

135-135: LGTM! Label change improves clarity.

The update from "Environments" to "Global Environments" better distinguishes this from collection-level environments. The tab id remains unchanged, avoiding breaking changes.

tests/global-environments/non-string-values.spec.ts (1)

2-2: LGTM! Good use of shared helper utility.

The migration to addEnvironmentVariables improves test maintainability and follows the pattern of preferring shared utilities over repeated setup code.

Also applies to: 29-33

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

1-8: LGTM!

Clean wrapper component that follows coding guidelines. The component correctly serves as the entry point for global environment settings in the new tab-based UI.

packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/StyledWrapper.js (1)

1-134: LGTM!

StyledWrapper correctly implements the inline rename UI with proper theme-based styling. Color management uses theme props as expected, and the layout structure supports both normal and renaming states effectively.

tests/environments/multiline-variables/write-multiline-variable.spec.ts (4)

14-14: Double-click interaction aligns with updated request opening pattern.

The change from single click to double-click for opening requests is consistent with the tab-based UI flow introduced in this PR.


31-36: Tab-based environment interaction with direct row creation.

The test correctly verifies the Environments tab visibility and directly creates a variable row via the empty row input, which is cleaner than the previous approach. Good use of locator variables.


53-56: CodeMirror-based value entry is appropriate for multiline JSON.

The approach of locating the variable row, finding CodeMirror within it, and using keyboard.insertText correctly handles multiline JSON input.


58-61: Test-id based save and close actions.

The migration to test-id based interactions (save-env, request-tab-close-icon) improves test reliability and aligns with the tab-based environment settings flow.

packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (9)

1-1: useCallback and useRef imports are appropriate.

These hooks support the draft-aware, event-driven save pattern introduced in this component.


13-17: Global environment actions imported correctly.

The imports for saveGlobalEnvironment, setGlobalEnvironmentsDraft, and clearGlobalEnvironmentsDraft align with the new draft management system.


24-32: Draft state and environment tracking setup.

The implementation correctly tracks the current environment's draft state and uses refs to detect environment switches for draft restoration.


41-56: Initial values use useMemo with correct dependencies.

The memoization correctly depends on environment.uid and environment.variables, ensuring the form reinitializes only when the environment changes or its saved variables change.


101-122: Draft restoration logic on mount and environment switch.

The effect correctly distinguishes between mount and environment change, restoring draft values only when appropriate. The trailing empty row is consistently appended.


124-150: Draft synchronization with Redux.

The effect properly compares current and saved values, dispatching draft actions only when changes occur. The comparison avoids unnecessary dispatches by checking existing draft JSON.


231-240: Simplified name blur and keydown handlers.

These handlers appropriately mark the field as touched for validation. The Enter key behavior is clear.


242-288: Comprehensive save validation and error handling.

The save handler correctly validates changes, checks for validation errors, dispatches the save action, and handles success/error cases with appropriate toast messages.


307-320: Event-driven save pattern with useRef.

The handleSaveRef pattern correctly maintains a stable event listener while allowing the handler to access the latest closure. This is the appropriate approach for window events in React.

packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (9)

1-1: useCallback and useRef imports are appropriate.

These hooks support the draft-aware, event-driven save pattern for collection environments.


13-16: Collection-specific actions imported correctly.

The imports for saveEnvironment, setEnvironmentsDraft, and clearEnvironmentsDraft align with the collection environment draft system.


23-28: Collection draft state tracking.

The component correctly accesses collection.environmentsDraft and tracks draft state for the current environment.


61-72: Yup validation schema is correct; ignore static analysis false positive.

The Biome linter incorrectly flags the .then() method as suspicious. In Yup schemas, .when() accepts a configuration object with then and otherwise properties—this is the documented API.


103-124: Draft restoration on mount and environment switch.

The logic correctly restores draft values when mounting or switching environments, maintaining the trailing empty row.


126-153: Draft sync with collection-specific actions.

The effect properly synchronizes draft state with Redux using collection UID, avoiding unnecessary dispatches through JSON comparison.


246-291: Save handler with collection-specific dispatch.

The handler correctly validates, dispatches saveEnvironment with collection UID, and handles success/error states appropriately.


311-324: Event-driven save with stable listener reference.

The handleSaveRef pattern ensures the window event listener remains stable while accessing the latest handler closure.


346-346: test-id on table row improves test reliability.

Adding data-testid with the variable name makes it easier to target specific rows in tests, as seen in the multiline-variables test.

packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/StyledWrapper.js (1)

84-84: Error tooltip alignment improved.

Changing left: 20px aligns the error tooltip with the header's horizontal padding, creating consistent visual alignment with the content.

tests/import/insomnia/import-insomnia-v4-environments.spec.ts (1)

181-184: Tab-based close interaction aligns with new UI pattern.

The test correctly closes the Environments tab using the hover + click close icon pattern, replacing the previous modal-based close action. Good use of locator variable and test-id.

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

42-42: Test-id based tab close improves test reliability.

Switching from class-based selector (.close-icon) to test-id (request-tab-close-icon) makes tests more resilient to styling changes and aligns with best practices.

tests/import/insomnia/import-insomnia-v5-environments.spec.ts (1)

205-209: LGTM!

The tab-based close flow is well-implemented using proper Playwright patterns: locator variable, filter, hover, and testId-based close action. Good use of test.step for report readability.

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

1-52: LGTM!

The styled component follows all coding guidelines and learnings: uses theme props for colors, applies proper layout structure, and maintains consistent spacing. The empty-state and shared-button classes provide reusable styling with proper transitions and hover states.

Based on learnings, styled components are correctly used for defining component and children styles with theme-based color management.

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

37-38: LGTM!

Import statements follow coding standards with proper formatting and organization.

tests/environments/collection-env-config-selection/collection-env-config-selection.spec.ts (1)

31-38: LGTM! Clean tab-based navigation implementation.

The transition from modal-based to tab-based environment configuration is implemented correctly. The locator for the Environments tab is well-defined, and the close mechanism using hover + testId is appropriate.

tests/environments/api-setEnvVar/multiple-persist-vars.spec.ts (2)

17-30: LGTM! Cleanup flow updated correctly.

The cleanup logic now properly closes the Environments tab instead of a modal. The implementation aligns with the new tab-based navigation pattern.


79-88: Good addition of tab visibility check.

Adding the explicit visibility assertion before interacting with environment variables improves test reliability.

tests/environments/import-environment/bruno-env-import/collection-env-import/collection-env-import.spec.ts (2)

42-49: LGTM! Import verification correctly updated.

The test now properly verifies the Environments tab visibility after import and uses the tab-based close mechanism.


115-124: Good implementation of tab-based verification.

The switch to prod environment and subsequent verification using the tab-based approach is correctly implemented.

tests/environments/update-global-environment-via-script/global-env-update-via-script.spec.ts (2)

25-32: LGTM! Tab-based navigation correctly implemented.

The test step now opens and verifies the Global Environments tab instead of a modal.


58-62: LGTM! Tab close logic updated correctly.

The test properly closes the Global Environments tab using the new hover + click approach.

tests/environments/import-environment/bruno-env-import/global-env-import/global-env-import.spec.ts (2)

57-65: LGTM! Import verification correctly updated.

The test now properly verifies the Global Environments tab visibility and uses the tab-based close mechanism.


118-119: Good addition of tab visibility checks.

The explicit visibility assertions improve test reliability and align with the new tab-based UI pattern.

Also applies to: 140-148

tests/environments/export-environment/collection-env-export/collection-env-export.spec.ts (1)

39-45: LGTM! Tab-based navigation implemented correctly.

The test now verifies the Environments tab instead of a modal and uses the appropriate export button selector.

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

391-407: Excellent type-aware environment creation.

The updated createEnvironment function properly handles both collection and global environment types with appropriate input selectors and post-creation verification. The addition of verification steps improves test reliability.


424-461: Well-structured environment variable helpers.

The new addEnvironmentVariable and addEnvironmentVariables functions provide clean, reusable utilities for populating environment variables. Good use of test.step for better test reporting.


480-487: LGTM! closeEnvironmentPanel correctly updated.

The function now properly closes the environment tab using the hover + testId click pattern. The type parameter allows it to handle both collection and global environment tabs.

tests/environments/export-environment/global-env-export/global-env-export.spec.ts (1)

43-49: LGTM! Global environment export tests updated correctly.

All test steps now properly verify the Global Environments tab visibility and use appropriate selectors for the tab-based UI flow.

Also applies to: 100-106, 168-174, 226-231, 283-288, 338-343, 398-403

packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js (1)

1-190: LGTM - Comprehensive theme-driven styling.

The styling changes properly use theme props for all colors and follow the styled-components pattern. The flex layout, table structure, button variants, and input styling all align with the project's design system.

Based on learnings, styled components are correctly used for styling both wrapper and children, with theme props managing all colors.

packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js (1)

1-277: LGTM - Well-structured sidebar and list styling.

The styling comprehensively covers the environment list sidebar, search functionality, item states (active, renaming, creating), inline actions, and error messages. All colors properly use theme props, and the layout follows a clean flex-based structure.

Based on learnings, styled components correctly define both wrapper and children styles with theme-based colors.

packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js (1)

12-181: LGTM - Well-implemented inline rename functionality.

The inline rename implementation is solid with:

  • Comprehensive validation covering required, length, regex, and duplicates
  • Proper state management for rename flow
  • Good UX patterns (Enter to save, Escape to cancel, blur handling)
  • Correct use of onMouseDown preventDefault to prevent blur on button clicks
  • Toast notifications for user feedback
  • Clean conditional rendering between rename and display modes
packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js (2)

9-22: The .shared-button class is already defined in StyledWrapper.js (line 35) with complete styling. No issues found.

Likely an incorrect or invalid review comment.


24-24: The onClose prop removal is correctly handled. The component now accepts only collection, and the single parent usage in RequestTabPanel properly reflects this change without passing onClose.

Likely an incorrect or invalid review comment.

Comment on lines +26 to 31
await page.locator('button[title="Delete"]').first().click();
const confirmButton = page.getByRole('button', { name: 'Delete' });
if (await confirmButton.isVisible()) {
await confirmButton.click();
await page.getByText('×').click();
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Generic delete button selector may be brittle.

The selector button[title="Delete"] could match multiple delete buttons on the page. Consider scoping it to the environment list or using a more specific selector.

-          await page.locator('button[title="Delete"]').first().click();
+          const envList = page.locator('.environment-list'); // or appropriate container
+          await envList.locator('button[title="Delete"]').first().click();

Or verify that only one such button exists before clicking:

const deleteButtons = page.locator('button[title="Delete"]');
await expect(deleteButtons).toHaveCount(1);
await deleteButtons.click();
🤖 Prompt for AI Agents
In
tests/environments/import-environment/bruno-env-import/global-env-import/global-env-import.spec.ts
around lines 26 to 31, the test uses a generic selector button[title="Delete"]
which may match multiple delete buttons and be brittle; update the test to scope
the selector to the specific environment list or container (e.g., locate the
environment row/card first and then find the Delete button within it) or
validate uniqueness before clicking by querying the locator count and asserting
it equals 1, then perform the click on that scoped/single locator so the test
targets the intended delete button reliably.

Comment on lines +90 to 94
await page.locator('button[title="Delete"]').first().click();
const confirmButton = page.getByText('Delete', { exact: true });
if (await confirmButton.isVisible()) {
await confirmButton.click();
await page.getByText('×').click();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Same delete button selector concern.

This has the same potential brittleness as the earlier delete button usage. Consider applying the same scoping suggestion.

Copy link
Collaborator

@bijin-bruno bijin-bruno left a comment

Choose a reason for hiding this comment

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

  • Unable to scroll and view save button when we have too many variables and the table size is more than view height

Copy link
Collaborator

Choose a reason for hiding this comment

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

What is the alternative for this info display?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we can keep this file tp components/Environments/ConfirmCloseEnvironment/index.js

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

♻️ Duplicate comments (3)
tests/global-environments/non-string-values.spec.ts (1)

75-75: Remove unnecessary .nth(0) now that selectors are scoped to rows.

Since the selectors are now scoped to numericRow and booleanRow respectively, the .nth(0) is redundant. Each row should only contain one disabled-info-icon, making the index unnecessary and improving readability.

Apply this refactor:

-    const infoIcon = numericRow.locator('[id$="-disabled-info-icon"]').nth(0);
+    const infoIcon = numericRow.locator('[id$="-disabled-info-icon"]');

And for the boolean variable:

-    const infoIcon = booleanRow.locator('[id$="-disabled-info-icon"]').nth(0);
+    const infoIcon = booleanRow.locator('[id$="-disabled-info-icon"]');

Also applies to: 102-102

packages/bruno-app/src/components/RequestTabs/RequestTab/index.js (2)

251-266: Missing feedback when draft is incomplete (flagged previously).

As noted in the earlier review, if draft?.environmentUid or draft?.variables is missing, the save handler does nothing and the modal remains open without user feedback.

Add an else branch to handle incomplete drafts:

 onSaveAndClose={() => {
   const draft = collection.environmentsDraft;
   if (draft?.environmentUid && draft?.variables) {
     dispatch(saveEnvironment(draft.variables, draft.environmentUid, collection.uid))
       .then(() => {
         dispatch(clearEnvironmentsDraft({ collectionUid: collection.uid }));
         dispatch(closeTabs({ tabUids: [tab.uid] }));
         setShowConfirmEnvironmentClose(false);
         toast.success('Environment saved');
       })
       .catch((err) => {
         console.log('err', err);
         toast.error('Failed to save environment');
       });
+  } else {
+    toast.error('Incomplete environment draft — cannot save');
+    setShowConfirmEnvironmentClose(false);
   }
 }}

278-294: Missing feedback when global draft is incomplete (flagged previously).

As noted in the earlier review, if the global environment draft is missing required fields, the save handler silently does nothing and the modal remains open.

Add an else branch to mirror the collection environment handling:

 onSaveAndClose={() => {
   const draft = globalEnvironmentDraft;
   if (draft?.environmentUid && draft?.variables) {
     dispatch(saveGlobalEnvironment({ variables: draft.variables, environmentUid: draft.environmentUid }))
       .then(() => {
         dispatch(clearGlobalEnvironmentDraft());
         dispatch(closeTabs({ tabUids: [tab.uid] }));
         setShowConfirmGlobalEnvironmentClose(false);
         toast.success('Global environment saved');
       })
       .catch((err) => {
         console.log('err', err);
         toast.error('Failed to save global environment');
       });
+  } else {
+    toast.error('Incomplete global environment draft — cannot save');
+    setShowConfirmGlobalEnvironmentClose(false);
   }
 }}
🧹 Nitpick comments (12)
packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js (1)

5-5: Consider adding JSDoc for component documentation.

A brief JSDoc comment would help other developers understand the component's purpose and usage, especially in the context of the tab-based environment flow.

As per coding guidelines, add JSDoc to provide more details:

+/**
+ * Confirmation modal displayed when attempting to close an environment tab with unsaved changes.
+ * Provides options to save, discard, or cancel the close action.
+ *
+ * @param {Function} onCancel - Called when user clicks Cancel
+ * @param {Function} onCloseWithoutSave - Called when user clicks Don't Save
+ * @param {Function} onSaveAndClose - Called when user clicks Save
+ * @param {boolean} isGlobal - Whether this is for global or collection environment
+ */
 const ConfirmCloseEnvironment = ({ onCancel, onCloseWithoutSave, onSaveAndClose, isGlobal }) => {
tests/global-environments/non-string-values.spec.ts (1)

26-26: Consider extracting the repeated envTab locator.

The locator pattern .request-tab with filter { hasText: 'Global Environments' } appears three times in the test. Extracting it to a variable at the test scope would improve maintainability and reduce duplication.

Example refactor:

 test('should seed non-string globals via request and verify read-only + tooltip', async ({
   pageWithUserData: page
 }) => {
+  const getEnvTab = () => page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+
   await openCollectionAndAcceptSandbox(page, 'global-env-non-string', 'safe');

   await test.step('Create a new global environment with a string variable', async () => {
     // ...
-    const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+    const envTab = getEnvTab();

Also applies to: 53-53, 125-125

packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js (1)

276-276: Avoid fragile color string concatenation.

Appending 15 to the color value assumes it's in hex format. If the theme ever uses rgb(), hsl(), or other formats, this will break.

Consider using a CSS color-mix or rgba transformation helper from your theme/utils, or apply opacity via a separate opacity property if the theme supports it.

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

262-262: Consider structured error logging.

Console.log is used for error handling in the catch blocks. While this follows the existing pattern in the file (lines 209, 237, 407), consider using a structured logging service for better observability in production.

Also applies to: 289-289

packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (5)

21-21: Remove unused originalEnvironmentVariables prop.

This parameter is destructured but never used in the component. Remove it to keep the API clean.

-const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentVariables, collection }) => {
+const EnvironmentVariables = ({ environment, setIsModified, collection }) => {

61-77: Yup $isLastRow context is never provided — this validation logic is dead code.

The when('$isLastRow', ...) requires a validation context to be passed (e.g., via validateAt or validate with { context: { isLastRow } }). Since formik doesn't receive this context, the conditional always falls through to otherwise. The custom validate function at lines 78-96 handles this logic correctly, making this Yup schema redundant.

Simplify by removing the non-functional conditional:

  validationSchema: Yup.array().of(Yup.object({
    enabled: Yup.boolean(),
-   name: Yup.string()
-     .when('$isLastRow', {
-       is: true,
-       then: (schema) => schema.optional(),
-       otherwise: (schema) => schema
-         .required('Name cannot be empty')
-         .matches(variableNameRegex,
-           'Name contains invalid characters. Must only contain alphanumeric characters, "-", "_", "." and cannot start with a digit.')
-         .trim()
-     }),
+   name: Yup.string(),
    secret: Yup.boolean(),
    type: Yup.string(),
    uid: Yup.string(),
    value: Yup.mixed().nullable()
  })),

101-122: Missing formik.setValues in effect dependency array.

The effect calls formik.setValues but doesn't include it in dependencies. While formik's setValues is typically stable, the linter will flag this. Consider suppressing with a comment or using a ref pattern for clarity:

+ const setValuesRef = useRef(formik.setValues);
+ setValuesRef.current = formik.setValues;

  React.useEffect(() => {
    // ...
    if ((isMount || envChanged) && hasDraftForThisEnv && globalEnvironmentDraft?.variables) {
-     formik.setValues([
+     setValuesRef.current([
        ...globalEnvironmentDraft.variables,
        // ...
      ]);
    }
- }, [environment.uid, hasDraftForThisEnv, globalEnvironmentDraft?.variables]);
+ }, [environment.uid, hasDraftForThisEnv, globalEnvironmentDraft?.variables]);

Alternatively, add an eslint-disable comment if the current behavior is intentional.


225-227: setTimeout with stale closure risk.

formik.values.length is captured when setTimeout is scheduled. If rapid inputs occur, the index could be stale. Consider using a callback pattern or formik's arrayHelpers instead:

-     setTimeout(() => {
-       formik.setFieldValue(formik.values.length, newVariable, false);
-     }, 0);
+     const currentLength = formik.values.length;
+     setTimeout(() => {
+       formik.setFieldValue(currentLength, newVariable, false);
+     }, 0);

This makes the capture explicit, though the underlying race condition remains. If dynamic arrays become more complex, consider FieldArray.


152-173: Inline component recreated on every render.

ErrorMessage is defined inside the component body, causing React to treat it as a new component type on each render. For small tables this is fine, but consider extracting it outside the component or memoizing if performance becomes a concern.

packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (3)

33-38: Consider memoizing the collection cloning.

The collection is cloned on every render, which could be expensive for large collections. Since this depends on collection and globalEnvironmentVariables, wrap this in useMemo.

-  let _collection = collection ? cloneDeep(collection) : {};
-
-  const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
-  if (_collection) {
-    _collection.globalEnvironmentVariables = globalEnvironmentVariables;
-  }
+  const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
+  
+  const _collection = useMemo(() => {
+    const cloned = collection ? cloneDeep(collection) : {};
+    if (cloned) {
+      cloned.globalEnvironmentVariables = globalEnvironmentVariables;
+    }
+    return cloned;
+  }, [collection, globalEnvironmentVariables]);

279-281: Consider if setTimeout is necessary.

The setTimeout(0) is used to defer the new row addition, likely to avoid state update conflicts. If you encounter issues, consider using formik.setValues with a callback or ensure this pattern is actually needed.


296-342: LGTM - Consider extracting validation logic.

The save flow is correct and handles errors appropriately. For maintainability, you could extract the validation logic (lines 306-314) into a separate helper function, but this is optional.

Example:

const validateVariables = (variables) => {
  return variables.some((variable) => {
    if (!variable.name || variable.name.trim() === '') return true;
    if (!variableNameRegex.test(variable.name)) return true;
    return false;
  });
};

const handleSave = () => {
  // ... filtering logic ...
  if (validateVariables(variablesToSave)) {
    toast.error('Please fix validation errors before saving');
    return;
  }
  // ... rest of save logic ...
};
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 48341fa and 17c732b.

📒 Files selected for processing (13)
  • packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js (1 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (4 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/RequestTabPanel/index.js (3 hunks)
  • packages/bruno-app/src/components/RequestTabs/RequestTab/index.js (5 hunks)
  • packages/bruno-app/src/components/RequestTabs/index.js (0 hunks)
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (9 hunks)
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js (3 hunks)
  • tests/environments/import-environment/global-env-import.spec.ts (1 hunks)
  • tests/global-environments/non-string-values.spec.ts (7 hunks)
💤 Files with no reviewable changes (1)
  • packages/bruno-app/src/components/RequestTabs/index.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js
  • tests/environments/import-environment/global-env-import.spec.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

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

Files:

  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js
  • packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/StyledWrapper.js
  • packages/bruno-app/src/components/RequestTabPanel/index.js
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/StyledWrapper.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/StyledWrapper.js
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
  • tests/global-environments/non-string-values.spec.ts
  • packages/bruno-app/src/components/RequestTabs/RequestTab/index.js
tests/**/**.*

⚙️ CodeRabbit configuration file

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

  • Follow best practices for Playwright code and e2e automation

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

  • Avoid using page.pause() in code

  • Use locator variables for locators

  • Avoid using test.only

  • Use multiple assertions

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

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

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

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

Files:

  • tests/global-environments/non-string-values.spec.ts
🧠 Learnings (5)
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{jsx,tsx} : Styled Components are used as wrappers to define both self and children components style; Tailwind classes are used specifically for layout based styles

Applied to files:

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

Applied to files:

  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/StyledWrapper.js
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/StyledWrapper.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/StyledWrapper.js
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{jsx,tsx} : Styled Component CSS might also change layout but Tailwind classes shouldn't define colors

Applied to files:

  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/StyledWrapper.js
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/StyledWrapper.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/StyledWrapper.js
📚 Learning: 2025-12-02T20:48:05.670Z
Learnt from: NikHillAgar
Repo: usebruno/bruno PR: 6279
File: packages/bruno-app/src/components/RequestPane/PromptVariables/PromptVariablesModal/index.js:79-86
Timestamp: 2025-12-02T20:48:05.670Z
Learning: In React 19+, ref callbacks support returning cleanup functions. When a ref callback returns a function, React will call it when the element unmounts or the ref changes, similar to useEffect cleanup. This is documented at https://react.dev/learn/manipulating-the-dom-with-refs#how-to-manage-a-list-of-refs-using-a-ref-callback

Applied to files:

  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.test.{js,jsx,ts,tsx} : Add tests for any new functionality or meaningful changes. If code is added, removed, or significantly modified, corresponding tests should be updated or created

Applied to files:

  • tests/global-environments/non-string-values.spec.ts
🧬 Code graph analysis (2)
packages/bruno-app/src/components/RequestTabPanel/index.js (4)
packages/bruno-app/src/components/RequestTabs/index.js (1)
  • activeTabUid (23-23)
packages/bruno-app/src/hooks/useTabPaneBoundaries/index.js (1)
  • focusedTab (11-11)
packages/bruno-app/src/components/Environments/GlobalEnvironmentSettings/index.js (1)
  • GlobalEnvironmentSettings (4-6)
packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js (1)
  • EnvironmentSettings (24-67)
packages/bruno-app/src/components/RequestTabs/RequestTab/index.js (6)
packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js (1)
  • tab (27-27)
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (2)
  • useSelector (24-24)
  • dispatch (22-22)
packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js (1)
  • ConfirmCloseEnvironment (5-41)
packages/bruno-app/src/components/RequestTabs/index.js (1)
  • dispatch (16-16)
packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js (2)
  • saveGlobalEnvironment (186-220)
  • saveGlobalEnvironment (186-220)
packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js (1)
  • SpecialTab (5-86)
🪛 Biome (2.1.2)
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js

[error] 113-113: Do not add then to an object.

(lint/suspicious/noThenProperty)

🔇 Additional comments (25)
packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/StyledWrapper.js (1)

6-6: LGTM! Overflow constraints align with tab-based layout.

The overflow: hidden additions on both the wrapper and container properly constrain the layout boundaries for the tab-based environment UI. The nested .environments-list retains overflow-y: auto (line 106) for internal scrolling, which is the correct hierarchy.

Also applies to: 14-14

packages/bruno-app/src/components/Environments/EnvironmentSettings/StyledWrapper.js (3)

1-2: LGTM!

Standard styled-components import is correct.


4-34: LGTM!

Container and empty-state styling is well-structured. Proper use of theme props for all colors, clean flexbox layout for centering, and good visual hierarchy with SVG opacity.


36-50: LGTM!

Shared button styling is clean and consistent. Proper theme prop usage, smooth hover transitions, and appropriate cursor behavior.

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

37-38: LGTM!

Imports correctly reference the new environment settings components from the Environments folder.


150-156: LGTM!

Good placement of the global-environment-settings check before the collectionUid validation. Since GlobalEnvironmentSettings doesn't require a collection context, this ordering avoids unnecessary guards and correctly short-circuits to the component.

Past review concern addressed: collection prop is no longer passed.


158-160: LGTM!

Guard correctly positioned after global-environment-settings (which doesn't require collectionUid) and before collection-dependent tab types.


209-211: LGTM!

Correctly placed after the collection existence guard (line 162). The collection prop is properly passed as required by the EnvironmentSettings component. Follows the established pattern used by other collection-dependent tabs like CollectionSettings and SecuritySettings.

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

1-3: Imports look good.

Clean and minimal imports. All used in the component.

tests/global-environments/non-string-values.spec.ts (1)

1-130: LGTM! Well-structured test with good practices.

The test follows Playwright best practices effectively:

  • Excellent use of test.step for clear test flow
  • Multiple assertions throughout
  • No usage of test.only, page.pause(), or unnecessary page.waitForTimeout()
  • Good use of locator variables
  • Successfully migrated from modal-based to tab-based UI interactions
packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/StyledWrapper.js (1)

7-7: No changes needed; scrolling is properly handled by child components.

The child components (EnvironmentList, EnvironmentDetails) implement their own scroll containers with overflow-y: auto, so the overflow: hidden on the main wrapper is the correct pattern for this flex-based layout.

Likely an incorrect or invalid review comment.

packages/bruno-app/src/components/RequestTabs/RequestTab/index.js (6)

4-7: LGTM: Imports correctly support environment tab functionality.

The new imports are properly structured and necessary for the environment-settings and global-environment-settings tab features.

Also applies to: 9-9, 16-16, 27-27


38-39: LGTM: State declarations follow existing patterns.

The confirmation dialog state variables are consistent with the established naming convention for collection and folder confirmations.


157-159: LGTM: Draft detection logic is consistent.

The environment draft flags mirror the existing collection and folder draft detection patterns, and the useSelector usage follows Redux best practices.


161-179: LGTM: Close handlers properly guard against unnecessary confirmations.

Both handlers correctly check for draft existence before showing the confirmation dialog, matching the established pattern for collection and folder settings.


181-181: LGTM: Tab type list updated correctly.

The new environment-related tab types are properly included in the special tab routing logic.


302-305: LGTM: Environment tab rendering is properly integrated.

The SpecialTab rendering for both environment types correctly passes the appropriate close handlers and draft flags, following the established pattern for other special tab types.

packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (5)

124-150: Good optimization to avoid redundant draft dispatches.

The comparison against existingDraftJson before dispatching prevents unnecessary Redux updates. The logic correctly handles both setting and clearing draft state.


175-210: LGTM.

The callback correctly handles edge cases: prevents removing the trailing empty row and ensures one always exists after removal.


307-320: Well-implemented event listener with ref pattern.

Using handleSaveRef to avoid recreating the listener while still accessing the latest handleSave is the correct approach. Cleanup is properly handled.


322-438: LGTM.

Clean table rendering with proper conditional logic for the trailing empty row. Event handlers and props are correctly wired.


242-288: LGTM.

Save logic correctly validates before dispatch, handles success/error states, and resets form with new values including the trailing empty row.

packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (3)

153-174: Verify draft restoration doesn't cause unnecessary re-renders.

The draft restoration effect depends on environmentsDraft?.variables, which changes when the sync effect (lines 177-203) dispatches setEnvironmentsDraft. Ensure this doesn't cause cascading effects or unnecessary restorations, especially during rapid typing.

The guards (isMount, envChanged) should prevent most issues, but monitor for edge cases.


361-374: LGTM - Good ref pattern for event handlers.

Using a ref to hold the latest handleSave ensures the event listener always invokes the current version without needing to re-register the listener. The cleanup is properly handled.


437-450: LGTM - Good UX for read-only non-string values.

The readOnly prop prevents editing of non-string values (set via scripts), and the info icon with tooltip clearly communicates this to users. Well done.

Comment on lines +191 to 268
.rename-container {
display: flex;
align-items: center;
flex: 1;

.environment-name-input {
flex: 1;
background: transparent;
border: none;
outline: none;
color: ${(props) => props.theme.text};
font-size: 13px;
padding: 2px 4px;

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

.inline-actions {
display: flex;
gap: 2px;
margin-left: 4px;
}
}

&.creating {
.environment-name-input {
flex: 1;
background: transparent;
border: none;
outline: none;
color: ${(props) => props.theme.text};
font-size: 13px;
padding: 2px 4px;

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

.inline-actions {
display: flex;
gap: 2px;
margin-left: 4px;
}

.inline-action-btn {
display: flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
padding: 0;
background: transparent;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.15s ease;

&.save {
color: ${(props) => props.theme.textLink};

&:hover {
background: ${(props) => props.theme.listItem.hoverBg};
}
}

&.cancel {
color: ${(props) => props.theme.colors.text.muted};

span:hover {
text-decoration: underline;
&:hover {
background: ${(props) => props.theme.listItem.hoverBg};
color: ${(props) => props.theme.text};
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Eliminate duplicate styles for rename and create modes.

The .environment-name-input (lines 196-208 vs 218-230) and .inline-actions (lines 210-214 vs 232-236) are duplicated between .renaming and .creating contexts. Additionally, .inline-action-btn (lines 238-267) is only defined inside .creating but likely needed for both modes.

Apply this diff to consolidate the shared styles:

     &.renaming,
     &.creating {
       cursor: default;
       padding: 4px 4px 4px 8px;
       background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
 
       &:hover {
         background: ${(props) => props.theme.workspace.button.bg};
       }
-    }
-
-    .rename-container {
-      display: flex;
-      align-items: center;
-      flex: 1;
 
       .environment-name-input {
         flex: 1;
         background: transparent;
         border: none;
         outline: none;
         color: ${(props) => props.theme.text};
         font-size: 13px;
         padding: 2px 4px;
 
         &::placeholder {
           color: ${(props) => props.theme.colors.text.muted};
         }
       }
 
       .inline-actions {
         display: flex;
         gap: 2px;
         margin-left: 4px;
       }
-    }
-
-    &.creating {
-      .environment-name-input {
-        flex: 1;
-        background: transparent;
-        border: none;
-        outline: none;
-        color: ${(props) => props.theme.text};
-        font-size: 13px;
-        padding: 2px 4px;
-
-        &::placeholder {
-          color: ${(props) => props.theme.colors.text.muted};
-        }
-      }
-
-      .inline-actions {
-        display: flex;
-        gap: 2px;
-        margin-left: 4px;
-      }
 
       .inline-action-btn {
         display: flex;
         align-items: center;
         justify-content: center;
         width: 22px;
         height: 22px;
         padding: 0;
         background: transparent;
         border: none;
         border-radius: 4px;
         cursor: pointer;
         transition: all 0.15s ease;
 
         &.save {
           color: ${(props) => props.theme.textLink};
 
           &:hover {
             background: ${(props) => props.theme.listItem.hoverBg};
           }
         }
 
         &.cancel {
           color: ${(props) => props.theme.colors.text.muted};
 
           &:hover {
             background: ${(props) => props.theme.listItem.hoverBg};
             color: ${(props) => props.theme.text};
           }
         }
       }
+    }
+
+    .rename-container {
+      display: flex;
+      align-items: center;
+      flex: 1;
     }
   }

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

🤖 Prompt for AI Agents
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js
lines 191-268: consolidate duplicated styles by extracting the shared
.environment-name-input and .inline-actions rules from inside both the
.rename-container and the &.creating blocks into a single shared selector (e.g.,
define .environment-name-input and .inline-actions once at the parent scope used
by both states), remove the duplicated blocks, and move the .inline-action-btn
rules currently only inside &.creating into the shared scope so both renaming
and creating modes use the same button styles; keep all theme prop references
unchanged and ensure specificity/ordering remains the same so hover and modifier
classes still apply.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
packages/bruno-app/src/components/RequestTabs/RequestTab/index.js (2)

255-270: Missing feedback when draft is incomplete.

The onSaveAndClose handler only executes when draft?.environmentUid && draft?.variables are truthy. If either is falsy, clicking "Save" does nothing and the modal stays open without user feedback.

🔎 Suggested fix
 onSaveAndClose={() => {
   const draft = collection.environmentsDraft;
   if (draft?.environmentUid && draft?.variables) {
     dispatch(saveEnvironment(draft.variables, draft.environmentUid, collection.uid))
       .then(() => {
         dispatch(clearEnvironmentsDraft({ collectionUid: collection.uid }));
         dispatch(closeTabs({ tabUids: [tab.uid] }));
         setShowConfirmEnvironmentClose(false);
         toast.success('Environment saved');
       })
       .catch((err) => {
         console.log('err', err);
         toast.error('Failed to save environment');
       });
+  } else {
+    toast.error('No environment changes to save');
+    setShowConfirmEnvironmentClose(false);
   }
 }}

282-298: Same issue: missing fallback for incomplete global environment draft.

Mirror the same handling pattern as collection environments for consistency.

🔎 Suggested fix
 onSaveAndClose={() => {
   const draft = globalEnvironmentDraft;
   if (draft?.environmentUid && draft?.variables) {
     dispatch(saveGlobalEnvironment({ variables: draft.variables, environmentUid: draft.environmentUid }))
       .then(() => {
         dispatch(clearGlobalEnvironmentDraft());
         dispatch(closeTabs({ tabUids: [tab.uid] }));
         setShowConfirmGlobalEnvironmentClose(false);
         toast.success('Global environment saved');
       })
       .catch((err) => {
         console.log('err', err);
         toast.error('Failed to save global environment');
       });
+  } else {
+    toast.error('No global environment changes to save');
+    setShowConfirmGlobalEnvironmentClose(false);
   }
 }}
🧹 Nitpick comments (1)
tests/utils/page/actions.ts (1)

399-410: Consider splitting verification logic from environment creation.

The function now does more than just "create" - it also opens the selector, switches tabs, selects the environment, and verifies selection. While thorough, this verification adds latency to every test using createEnvironment.

Consider:

  • Extracting lines 400-410 into a separate verifyEnvironmentCreated helper
  • Or making the post-creation verification optional via a parameter
  • This would allow tests that don't need full verification to run faster
Potential refactor approach
 const createEnvironment = async (
   page: Page,
   environmentName: string,
-  type: EnvironmentType = 'collection'
+  type: EnvironmentType = 'collection',
+  options: { verify?: boolean } = { verify: true }
 ) => {
   await test.step(`Create ${type} environment "${environmentName}"`, async () => {
     await openEnvironmentSelector(page, type);
     
     await page.locator('button[id="create-env"]').click();
     
     const nameInput = type === 'collection'
       ? page.locator('input[name="name"]')
       : page.locator('#environment-name');
     await expect(nameInput).toBeVisible();
     await nameInput.fill(environmentName);
     await page.getByRole('button', { name: 'Create' }).click();
 
-    const tabLabel = type === 'collection' ? 'Environments' : 'Global Environments';
-    await expect(page.locator('.request-tab').filter({ hasText: tabLabel })).toBeVisible();
-
-    const locators = buildCommonLocators(page);
-    await locators.environment.selector().click();
-    if (type === 'global') {
-      await locators.environment.globalTab().click();
-    }
-    await locators.environment.envOption(environmentName).click();
-    await expect(page.locator('.current-environment')).toContainText(environmentName);
+    if (options.verify) {
+      const tabLabel = type === 'collection' ? 'Environments' : 'Global Environments';
+      await expect(page.locator('.request-tab').filter({ hasText: tabLabel })).toBeVisible();
+
+      const locators = buildCommonLocators(page);
+      await locators.environment.selector().click();
+      if (type === 'global') {
+        await locators.environment.globalTab().click();
+      }
+      await locators.environment.envOption(environmentName).click();
+      await expect(page.locator('.current-environment')).toContainText(environmentName);
+    }
   });
 };
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 17c732b and 4284c03.

📒 Files selected for processing (9)
  • packages/bruno-app/src/components/RequestTabPanel/index.js (4 hunks)
  • packages/bruno-app/src/components/RequestTabs/RequestTab/index.js (5 hunks)
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/CreateEnvironment/index.js (2 hunks)
  • packages/bruno-app/src/components/WorkspaceHome/index.js (1 hunks)
  • packages/bruno-app/src/providers/ReduxStore/slices/app.js (0 hunks)
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js (2 hunks)
  • tests/request/newlines/newlines-persistence.spec.ts (1 hunks)
  • tests/utils/page/actions.ts (2 hunks)
  • tests/utils/page/locators.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/bruno-app/src/providers/ReduxStore/slices/app.js
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/CreateEnvironment/index.js
  • tests/request/newlines/newlines-persistence.spec.ts
  • tests/utils/page/locators.ts
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
  • packages/bruno-app/src/components/WorkspaceHome/index.js
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

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

Files:

  • packages/bruno-app/src/components/RequestTabs/RequestTab/index.js
  • tests/utils/page/actions.ts
  • packages/bruno-app/src/components/RequestTabPanel/index.js
tests/**/**.*

⚙️ CodeRabbit configuration file

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

  • Follow best practices for Playwright code and e2e automation

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

  • Avoid using page.pause() in code

  • Use locator variables for locators

  • Avoid using test.only

  • Use multiple assertions

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

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

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

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

Files:

  • tests/utils/page/actions.ts
🧠 Learnings (1)
📚 Learning: 2025-12-17T21:41:24.730Z
Learnt from: naman-bruno
Repo: usebruno/bruno PR: 6407
File: packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js:5-41
Timestamp: 2025-12-17T21:41:24.730Z
Learning: Do not suggest PropTypes validation for React components in the Bruno codebase. The project does not use PropTypes, so reviews should avoid proposing PropTypes and rely on the existing typing/validation approach (e.g., TypeScript or alternative runtime checks) if applicable. This guideline applies broadly to all JavaScript/JSX components in the repo.

Applied to files:

  • packages/bruno-app/src/components/RequestTabs/RequestTab/index.js
  • packages/bruno-app/src/components/RequestTabPanel/index.js
🧬 Code graph analysis (2)
packages/bruno-app/src/components/RequestTabs/RequestTab/index.js (3)
packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js (1)
  • ConfirmCloseEnvironment (5-41)
packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js (2)
  • saveEnvironment (1740-1778)
  • saveEnvironment (1740-1778)
packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js (2)
  • saveGlobalEnvironment (186-220)
  • saveGlobalEnvironment (186-220)
packages/bruno-app/src/components/RequestTabPanel/index.js (7)
packages/bruno-app/src/components/RequestTabs/index.js (1)
  • activeTabUid (23-23)
packages/bruno-app/src/pages/Bruno/index.js (1)
  • activeTabUid (57-57)
packages/bruno-app/src/providers/Hotkeys/index.js (1)
  • activeTabUid (27-27)
packages/bruno-app/src/components/RequestPane/GraphQLRequestPane/index.js (2)
  • activeTabUid (42-42)
  • focusedTab (56-56)
packages/bruno-app/src/hooks/useTabPaneBoundaries/index.js (1)
  • focusedTab (11-11)
packages/bruno-app/src/components/Environments/GlobalEnvironmentSettings/index.js (1)
  • GlobalEnvironmentSettings (4-6)
packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js (1)
  • EnvironmentSettings (24-67)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: CLI Tests
  • GitHub Check: Unit Tests
🔇 Additional comments (13)
packages/bruno-app/src/components/RequestTabPanel/index.js (3)

37-38: LGTM!

Imports are correctly added and both components are used in the render branches below.


174-184: LGTM!

The conditional ordering is correct. Global environment tabs are handled before the collectionUid validation since they don't require a collection context. This prevents false "error occurred" messages.


233-235: LGTM!

Correctly placed after collection validation. The collection prop is appropriately passed to EnvironmentSettings.

packages/bruno-app/src/components/RequestTabs/RequestTab/index.js (6)

4-7: LGTM!

Imports are correctly added for the new environment save/clear actions.


38-39: LGTM!

State variables follow the established pattern for close confirmation modals.


161-163: LGTM!

Draft detection follows the established pattern for collection and folder drafts.


165-183: LGTM!

Close handlers follow the established pattern for collection and folder settings.


306-309: LGTM!

SpecialTab branches for environment tabs follow the established pattern and correctly pass the hasDraft prop.


185-185: LGTM!

Tab type array correctly extended to include the new environment tab types.

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

353-353: LGTM - Clean type definition.

The EnvironmentType union type is well-defined and properly exported for use across test files.


393-398: LGTM - Conditional locator selection handles both environment types.

The conditional logic correctly selects the appropriate input locator based on environment type (input[name="name"] for collection, #environment-name for global).


482-489: LGTM - Tab-based closing aligns with modal-to-tabs migration.

The implementation correctly:

  • Determines the tab label based on environment type
  • Locates the tab using text filter
  • Hovers before clicking (ensures close icon visibility)
  • Uses testId for the close action

This change properly supports the PR's shift from modal dialogs to tab-based navigation.


756-756: LGTM - Proper type export for test reusability.

Exporting EnvironmentType allows test files to use type-safe parameters when calling these utility functions.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (4)

107-151: Redundant validation: schema's $isLastRow context is never passed.

The validationSchema uses when('$isLastRow', ...) but Formik isn't configured with validationContext to provide this value. The schema validation will always use the otherwise branch. Since the custom validate function (lines 129-149) correctly handles the last-row logic, the schema's conditional is effectively dead code.

Consider simplifying by removing the when clause from the schema and relying solely on the custom validate function, or remove the custom validate and properly pass context to the schema.

🔎 Simplified schema (relying on custom validate):
     validationSchema: Yup.array().of(
       Yup.object({
         enabled: Yup.boolean(),
-        name: Yup.string()
-          .when('$isLastRow', {
-            is: true,
-            then: (schema) => schema.optional(),
-            otherwise: (schema) =>
-              schema
-                .required('Name cannot be empty')
-                .matches(
-                  variableNameRegex,
-                  'Name contains invalid characters. Must only contain alphanumeric characters, "-", "_", "." and cannot start with a digit.'
-                )
-                .trim()
-          }),
+        name: Yup.string().trim(),
         secret: Yup.boolean(),
         type: Yup.string(),
         uid: Yup.string(),
         value: Yup.mixed().nullable()
       })
     ),

Note: The static analysis hint about then on line 113 is a false positive—Yup's .when() uses then as a configuration property, not a Promise method.


214-235: Consider extracting ErrorMessage outside the component.

This inner component is recreated on every render. While the impact is minimal for this simple presentational component, extracting it outside and passing formik as a prop would be cleaner and slightly more performant.


275-292: Potential stale closure in setTimeout.

Line 289 captures formik.values.length at call time, but the setTimeout executes asynchronously. If the user types rapidly, the captured length might be stale. In practice, setFieldValue with an out-of-bounds index appends correctly, so this works, but using a functional update pattern would be clearer.

🔎 Alternative using current values:
     if (isLastRow) {
       const newVariable = {
         uid: uuid(),
         name: '',
         value: '',
         type: 'text',
         secret: false,
         enabled: true
       };
       setTimeout(() => {
-        formik.setFieldValue(formik.values.length, newVariable, false);
+        formik.setValues((currentValues) => [...currentValues, newVariable]);
       }, 0);
     }

305-351: Consider using formik.validateForm() instead of duplicating validation.

Lines 315-327 duplicate the validation logic that already exists in the custom validate function. You could simplify by calling formik.validateForm() and checking if errors exist:

const errors = await formik.validateForm();
if (Object.keys(errors).length > 0) {
  toast.error('Please fix validation errors before saving');
  return;
}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4284c03 and 4a0777a.

📒 Files selected for processing (2)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (4 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

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

Files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
🧬 Code graph analysis (1)
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (3)
packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js (25)
  • collection (113-113)
  • collection (148-148)
  • collection (154-154)
  • collection (162-162)
  • collection (168-168)
  • collection (175-175)
  • collection (182-182)
  • collection (214-214)
  • collection (223-223)
  • environment (243-243)
  • environment (255-255)
  • environment (268-268)
  • vars (2185-2185)
  • vars (2194-2194)
  • vars (2214-2214)
  • vars (2224-2224)
  • vars (2245-2245)
  • vars (2249-2249)
  • vars (2416-2416)
  • vars (2425-2425)
  • vars (2447-2447)
  • vars (2457-2457)
  • vars (2479-2479)
  • vars (2483-2483)
  • window (2751-2751)
packages/bruno-app/src/utils/common/regex.js (2)
  • variableNameRegex (7-7)
  • variableNameRegex (7-7)
packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js (2)
  • saveEnvironment (1740-1778)
  • saveEnvironment (1740-1778)
🪛 Biome (2.1.2)
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js

[error] 113-113: Do not add then to an object.

(lint/suspicious/noThenProperty)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: SSL Tests - Linux
🔇 Additional comments (14)
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (7)

1-20: LGTM!

Imports are well-organized and appropriate for the draft-based editing functionality being introduced.


21-38: LGTM!

Component setup with draft tracking refs and collection cloning is solid. The hasDraftForThisEnv check correctly identifies when a draft exists for the current environment.


40-86: LGTM!

The memoized sensitive field detection is efficient and correctly identifies non-secret variables used in sensitive contexts. Good security practice.


187-212: Good improvement: debounced draft sync.

The 300ms debounce with proper cleanup addresses the previous performance concern about running on every keystroke. The additional check comparing currentValuesJson !== existingDraftJson before dispatching is a nice optimization to prevent redundant Redux updates.


370-383: LGTM!

The handleSaveRef pattern correctly maintains a stable reference to the latest handleSave while avoiding stale closures in the window event listener. Proper cleanup on unmount.


399-487: LGTM!

The table rendering correctly handles the trailing empty row pattern—hiding checkboxes and delete buttons for the placeholder row while maintaining a clean UX. The read-only treatment for non-string values with an explanatory tooltip is a nice touch.


492-501: LGTM!

Clean button layout with appropriate test IDs for automation. The inline Save/Reset pattern fits well with the tab-based editing paradigm.

packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js (7)

3-9: LGTM!

The wrapper structure correctly establishes a full-height flex container with relative positioning for the overlay pattern. Theme usage follows project standards.


10-26: LGTM!

The container and overlay structure is well-designed. The confirm-switch-overlay uses proper absolute positioning with z-index layering, and the border declaration is complete and correct.


28-68: LGTM!

The sidebar layout and header structure are well-organized with appropriate fixed width and flexbox behavior. Hover states and transitions are properly implemented using theme colors.


70-101: LGTM!

The search container implementation is correct with proper icon positioning and input styling. The border property has the complete declaration.


103-107: LGTM!

The environments-list container is properly configured for scrollable content with flex growth and vertical overflow handling.


109-179: LGTM!

The environment item base styling and action controls are well-implemented. The opacity-based show/hide pattern for actions on hover and the active state styling are appropriate.


249-256: LGTM!

The error message styling uses appropriate danger colors with a semi-transparent background for visual distinction. The compact sizing fits well within the list context.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/bruno-app/src/components/RequestTabPanel/index.js (1)

174-180: LGTM: Correct ordering for workspace-level global environment settings.

The enhanced guard at line 174 is good defensive programming. The early return for global-environment-settings (lines 178-180) correctly bypasses collection validation since global environments are workspace-level, not collection-scoped.

This intentional ordering distinguishes global settings from collection-scoped environment-settings (line 233), which properly validates collection existence first.

Optional: Add explanatory comment

Consider adding a brief comment before line 178 to document the ordering rationale:

+  // Global environment settings are workspace-level, no collection validation needed
   if (focusedTab.type === 'global-environment-settings') {
     return <GlobalEnvironmentSettings />;
   }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4a0777a and 5f11181.

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

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

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

Files:

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

Applied to files:

  • packages/bruno-app/src/components/RequestTabPanel/index.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Unit Tests
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: SSL Tests - Linux
🔇 Additional comments (3)
packages/bruno-app/src/components/RequestTabPanel/index.js (3)

37-38: LGTM: Clean imports for new environment tab types.

The imports follow the project conventions and are required for the new tab rendering paths below.


182-184: LGTM: Enhanced guard properly validates tab properties.

Checking both focusedTab.uid and focusedTab.collectionUid prevents downstream errors. Correctly placed after the global-environment-settings path (which doesn't require collection context) and before collection-specific logic.


233-235: LGTM: Environment settings correctly placed and scoped.

The render path for environment-settings is properly positioned after collection validation (lines 186-188) since these settings are collection-scoped. Passing the collection prop is appropriate and consistent with the component's requirements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js (1)

181-269: Critical: Fix broken CSS nesting and eliminate duplication.

There are two critical structural issues here:

  1. Broken nesting: The &.renaming, &.creating block closes at line 190, but .rename-container (line 192) and the second &.creating block (line 218) are siblings at the .environment-item level. This breaks scoping—.rename-container will apply regardless of state, and you have two separate &.creating blocks.

  2. Massive duplication: .environment-name-input and .inline-actions are defined identically in both .rename-container (lines 197-215) and the second &.creating block (lines 219-237). Additionally, .inline-action-btn (lines 239-268) only exists in &.creating but is likely needed for rename actions too.

🔎 Apply this diff to fix nesting and consolidate shared styles:
     &.renaming,
     &.creating {
       cursor: default;
       padding: 4px 4px 4px 8px;
       background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
       
       &:hover {
         background: ${(props) => props.theme.workspace.button.bg};
       }
-    }
-
-    .rename-container {
-      display: flex;
-      align-items: center;
-      flex: 1;
       
       .environment-name-input {
         flex: 1;
         background: transparent;
         border: none;
         outline: none;
         color: ${(props) => props.theme.text};
         font-size: 13px;
         padding: 2px 4px;
         
         &::placeholder {
           color: ${(props) => props.theme.colors.text.muted};
         }
       }
       
       .inline-actions {
         display: flex;
         gap: 2px;
         margin-left: 4px;
       }
-    }
-
-    &.creating {
-      .environment-name-input {
-        flex: 1;
-        background: transparent;
-        border: none;
-        outline: none;
-        color: ${(props) => props.theme.text};
-        font-size: 13px;
-        padding: 2px 4px;
-        
-        &::placeholder {
-          color: ${(props) => props.theme.colors.text.muted};
-        }
-      }
-      
-      .inline-actions {
-        display: flex;
-        gap: 2px;
-        margin-left: 4px;
-      }
       
       .inline-action-btn {
         display: flex;
         align-items: center;
         justify-content: center;
         width: 22px;
         height: 22px;
         padding: 0;
         background: transparent;
         border: none;
         border-radius: 4px;
         cursor: pointer;
         transition: all 0.15s ease;
         
         &.save {
           color: ${(props) => props.theme.textLink};
           
           &:hover {
             background: ${(props) => props.theme.listItem.hoverBg};
           }
         }
         
         &.cancel {
           color: ${(props) => props.theme.colors.text.muted};
           
           &:hover {
             background: ${(props) => props.theme.listItem.hoverBg};
             color: ${(props) => props.theme.text};
           }
         }
       }
+    }
+
+    .rename-container {
+      display: flex;
+      align-items: center;
+      flex: 1;
     }
   }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5f11181 and 9ac1b40.

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

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

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

Files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: CLI Tests
  • GitHub Check: Unit Tests
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: SSL Tests - Windows
🔇 Additional comments (3)
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js (3)

4-26: LGTM on root layout structure.

The flex-based layout with absolute overlay positioning is well-structured. Theme properties are used correctly for colors and borders.


110-180: LGTM on environment item base styles.

The item layout, text overflow handling, and action visibility states are well-implemented. Theme properties and transitions are used appropriately.


272-279: LGTM on error message styling.

The error state styling appropriately uses theme danger colors with semi-transparent background. The compact size and border radius are suitable for inline error display.

Comment on lines +76 to +82
position: absolute;
left: 20px;
top: 50%;
transform: translateY(-100%);
color: ${(props) => props.theme.colors.text.muted};
pointer-events: none;
}
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

Fix search icon vertical positioning.

Line 79 uses translateY(-100%) which moves the icon above the input field rather than centering it. For proper vertical centering with top: 50%, use translateY(-50%).

🔎 Apply this diff:
     .search-icon {
       position: absolute;
       left: 20px;
       top: 50%;
-      transform: translateY(-100%);
+      transform: translateY(-50%);
       color: ${(props) => props.theme.colors.text.muted};
       pointer-events: none;
     }
📝 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
position: absolute;
left: 20px;
top: 50%;
transform: translateY(-100%);
color: ${(props) => props.theme.colors.text.muted};
pointer-events: none;
}
position: absolute;
left: 20px;
top: 50%;
transform: translateY(-50%);
color: ${(props) => props.theme.colors.text.muted};
pointer-events: none;
}
🤖 Prompt for AI Agents
In
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js
around lines 76 to 82, the search icon is vertically misaligned because
translateY(-100%) is used with top: 50%; change the transform to
translateY(-50%) so the icon is vertically centered relative to the input;
update that single property and run the UI to verify the icon centers correctly.

Copy link
Collaborator

@bijin-bruno bijin-bruno left a comment

Choose a reason for hiding this comment

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

  1. cmd+s is not saving the changes when focused on env value fields
  2. Focus outline is cut off for the save button.
Screenshot 2025-12-18 at 2 53 30 PM


const ConfirmCloseEnvironment = ({ onCancel, onCloseWithoutSave, onSaveAndClose, isGlobal }) => {
return (
<Modal
Copy link
Collaborator

@bijin-bruno bijin-bruno Dec 18, 2025

Choose a reason for hiding this comment

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

Let's add Portal

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (5)
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (5)

104-151: Consider removing redundant validation logic.

Both the Yup validationSchema (lines 107-128) and the custom validate function (lines 129-149) enforce the same name validation rules. This duplication creates maintenance overhead and risks inconsistencies if one is updated without the other.

🔎 Simplify by relying solely on Yup schema

The Yup schema already handles conditional validation based on isLastRow. Consider removing the custom validate function and using only the schema, or consolidate the logic if the custom function provides additional checks not shown in the schema.

 const formik = useFormik({
   enableReinitialize: true,
   initialValues: initialValues,
   validationSchema: Yup.array().of(
     Yup.object({
       enabled: Yup.boolean(),
       name: Yup.string()
         .when('$isLastRow', {
           is: true,
           then: (schema) => schema.optional(),
           otherwise: (schema) =>
             schema
               .required('Name cannot be empty')
               .matches(
                 variableNameRegex,
                 'Name contains invalid characters. Must only contain alphanumeric characters, "-", "_", "." and cannot start with a digit.'
               )
               .trim()
         }),
       secret: Yup.boolean(),
       type: Yup.string(),
       uid: Yup.string(),
       value: Yup.mixed().nullable()
     })
   ),
-  validate: (values) => {
-    const errors = {};
-    values.forEach((variable, index) => {
-      const isLastRow = index === values.length - 1;
-      const isEmptyRow = !variable.name || variable.name.trim() === '';
-
-      if (isLastRow && isEmptyRow) {
-        return;
-      }
-
-      if (!variable.name || variable.name.trim() === '') {
-        if (!errors[index]) errors[index] = {};
-        errors[index].name = 'Name cannot be empty';
-      } else if (!variableNameRegex.test(variable.name)) {
-        if (!errors[index]) errors[index] = {};
-        errors[index].name
-          = 'Name contains invalid characters. Must only contain alphanumeric characters, "-", "_", "." and cannot start with a digit.';
-      }
-    });
-    return Object.keys(errors).length > 0 ? errors : {};
-  },
   onSubmit: () => {}
 });

Note: If the custom validate function is needed for runtime-specific checks, document why both are necessary.


176-212: Consider consolidating the two similar effects.

Two effects process formik.values with similar filtering and JSON comparison logic. The first (lines 180-185) updates isModified immediately, while the second (lines 187-212) debounces draft synchronization. While functional, this creates code duplication and unnecessary re-computation.

🔎 Potential consolidation approach

Consider combining into a single debounced effect that updates both isModified (via a state update outside the timeout) and draft persistence (inside the timeout):

useEffect(() => {
  const currentValues = formik.values.filter((variable) => variable.name && variable.name.trim() !== '');
  const currentValuesJson = JSON.stringify(currentValues);
  const hasActualChanges = currentValuesJson !== savedValuesJson;
  
  // Immediate UI feedback
  setIsModified(hasActualChanges);
  
  // Debounced draft sync
  const timeoutId = setTimeout(() => {
    const existingDraftVariables = hasDraftForThisEnv ? environmentsDraft?.variables : null;
    const existingDraftJson = existingDraftVariables ? JSON.stringify(existingDraftVariables) : null;

    if (hasActualChanges) {
      if (currentValuesJson !== existingDraftJson) {
        dispatch(setEnvironmentsDraft({
          collectionUid: collection.uid,
          environmentUid: environment.uid,
          variables: currentValues
        }));
      }
    } else if (hasDraftForThisEnv) {
      dispatch(clearEnvironmentsDraft({ collectionUid: collection.uid }));
    }
  }, 300);

  return () => clearTimeout(timeoutId);
}, [formik.values, savedValuesJson, environment.uid, collection.uid, dispatch, hasDraftForThisEnv, environmentsDraft?.variables, setIsModified]);

Alternatively, if immediate isModified feedback is critical, document why the separation is needed.


275-291: setTimeout for trailing row addition is fragile.

Using setTimeout with a 0ms delay (line 288) to add a new trailing row can introduce timing issues and race conditions, especially if the user types quickly or if multiple state updates occur concurrently.

Consider alternatives:

  • Use formik.setFieldValue synchronously if possible
  • Use a proper state management approach that doesn't rely on deferred execution
  • If the setTimeout is necessary to work around Formik's batching, add a comment explaining why

If testing confirms this works reliably across scenarios, document the reasoning to help future maintainers understand the timing constraint.


305-351: Replace manual validation with formik's built-in validation.

Lines 315-328 manually validate variables, duplicating the validation logic already defined in formik's schema and custom validate function. This creates maintenance overhead and risks divergence.

🔎 Use formik.validateForm() instead
 const handleSave = () => {
   const variablesToSave = formik.values.filter((variable) => variable.name && variable.name.trim() !== '');
   const savedValues = environment.variables || [];

   const hasChanges = JSON.stringify(variablesToSave) !== JSON.stringify(savedValues);
   if (!hasChanges) {
     toast.error('No changes to save');
     return;
   }

-  const hasValidationErrors = variablesToSave.some((variable) => {
-    if (!variable.name || variable.name.trim() === '') {
-      return true;
-    }
-    if (!variableNameRegex.test(variable.name)) {
-      return true;
-    }
-    return false;
-  });
-
-  if (hasValidationErrors) {
+  const errors = await formik.validateForm();
+  if (Object.keys(errors).length > 0) {
     toast.error('Please fix validation errors before saving');
     return;
   }

   dispatch(saveEnvironment(cloneDeep(variablesToSave), environment.uid, collection.uid))
     .then(() => {
       toast.success('Changes saved successfully');
       const newValues = [
         ...variablesToSave,
         {
           uid: uuid(),
           name: '',
           value: '',
           type: 'text',
           secret: false,
           enabled: true
         }
       ];
       formik.resetForm({ values: newValues });
       setIsModified(false);
     })
     .catch((error) => {
       console.error(error);
       toast.error('An error occurred while saving the changes');
     });
 };

Note: If you use this approach, make handleSave async or handle the validateForm promise appropriately.


370-383: Custom window event pattern for save is functional but non-idiomatic.

The component listens for a custom 'environment-save' window event, using a ref to maintain the latest handleSave. While this works, it couples the component to a global event system. Consider using React patterns like useImperativeHandle with a forwarded ref if you refactor component communication in the future.

For now, this is functional and may be consistent with patterns elsewhere in the codebase.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ac1b40 and ba0d57d.

📒 Files selected for processing (5)
  • packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js (1 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (4 hunks)
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (10 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js
  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

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

Files:

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

Applied to files:

  • packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js
📚 Learning: 2025-12-02T20:48:05.670Z
Learnt from: NikHillAgar
Repo: usebruno/bruno PR: 6279
File: packages/bruno-app/src/components/RequestPane/PromptVariables/PromptVariablesModal/index.js:79-86
Timestamp: 2025-12-02T20:48:05.670Z
Learning: In React 19+, ref callbacks support returning cleanup functions. When a ref callback returns a function, React will call it when the element unmounts or the ref changes, similar to useEffect cleanup. This is documented at https://react.dev/learn/manipulating-the-dom-with-refs#how-to-manage-a-list-of-refs-using-a-ref-callback

Applied to files:

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

Applied to files:

  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{jsx,tsx} : Styled Component CSS might also change layout but Tailwind classes shouldn't define colors

Applied to files:

  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.{jsx,tsx} : Styled Components are used as wrappers to define both self and children components style; Tailwind classes are used specifically for layout based styles

Applied to files:

  • packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js
🪛 Biome (2.1.2)
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js

[error] 113-113: Do not add then to an object.

(lint/suspicious/noThenProperty)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Unit Tests
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: CLI Tests
🔇 Additional comments (5)
packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js (1)

142-142: LGTM! Minor padding adjustment for improved layout.

The 2px horizontal padding prevents buttons from being flush against the container edges in the new tab-based UI.

packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (1)

307-320: Nice pattern for global save event handling.

Using a ref to store the latest handleSave function prevents stale closures in the window event listener. This is a clean way to integrate with the global save flow.

packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js (3)

1-21: LGTM! Component signature aligns with tab-based editing.

The updated signature removing onClose and originalEnvironmentVariables while adding setIsModified correctly reflects the shift from modal to tab-based editing. Draft-related imports support the new draft persistence flow.


153-174: LGTM! Draft restoration logic handles mount and environment switches correctly.

The use of refs to track previous environment UID and mount state is appropriate. The effect correctly restores draft values when switching environments or on initial mount, and properly maintains the trailing empty row.


385-504: LGTM! UI rendering is well-structured with good UX considerations.

The table rendering with inline editing, conditional tooltips for read-only non-string values, and sensitive field warnings provides a clear user experience. The trailing empty row pattern is handled cleanly.

import { Tooltip } from 'react-tooltip';
import { getGlobalEnvironmentVariables } from 'utils/collections';

const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentVariables, collection }) => {
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

Remove unused prop originalEnvironmentVariables.

The originalEnvironmentVariables prop is declared but never referenced in the component. This is likely leftover from the previous modal-based implementation.

🔎 Apply this diff to remove the unused prop:
-const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentVariables, collection }) => {
+const EnvironmentVariables = ({ environment, setIsModified, collection }) => {
🤖 Prompt for AI Agents
In
packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
around line 21, the component signature includes an unused prop
originalEnvironmentVariables; remove originalEnvironmentVariables from the
function parameter list and from any related prop-type/default export/type
definitions or callers if present so the component only accepts and uses
environment, setIsModified and collection; run linter/tests to ensure no
remaining references to originalEnvironmentVariables remain.

Comment on lines +101 to +122
// Restore draft values on mount or environment switch
React.useEffect(() => {
const isMount = !mountedRef.current;
const envChanged = prevEnvUidRef.current !== null && prevEnvUidRef.current !== environment.uid;

prevEnvUidRef.current = environment.uid;
mountedRef.current = true;

if ((isMount || envChanged) && hasDraftForThisEnv && globalEnvironmentDraft?.variables) {
formik.setValues([
...globalEnvironmentDraft.variables,
{
uid: uuid(),
name: '',
value: '',
type: 'text',
secret: false,
enabled: true
}
]);
}
}, [environment.uid, hasDraftForThisEnv, globalEnvironmentDraft?.variables]);
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

Include formik in the dependency array.

The formik object's helper methods are referentially unstable across renders, meaning formik.setValues is a true dependency. While this may cause additional effect runs, this is the correct behavior. Alternatively, if extra runs are undesirable, extract and memoize only the setValues method to break the dependency cycle.

🤖 Prompt for AI Agents
In
packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
around lines 101 to 122, the effect depends on formik.setValues but formik is
not listed in the dependency array; add formik (or at minimum formik.setValues)
to the dependency array so the effect re-runs when the formik helpers change, or
alternatively extract and memoize formik.setValues outside the effect and use
that memoized setter in the dependency array to avoid unnecessary runs.

Copy link
Collaborator

@bijin-bruno bijin-bruno left a comment

Choose a reason for hiding this comment

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

We should create a common table for both collection and global environments and also reuse the same for the workspace environment.
Manage Secrets info modal is also missing.
@naman-bruno To be done in a follow-up PR

@bijin-bruno bijin-bruno merged commit bc2efb9 into usebruno:main Dec 18, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants