Skip to content

init: workspaces#6264

Merged
bijin-bruno merged 11 commits intousebruno:mainfrom
naman-bruno:workspaces
Dec 3, 2025
Merged

init: workspaces#6264
bijin-bruno merged 11 commits intousebruno:mainfrom
naman-bruno:workspaces

Conversation

@naman-bruno
Copy link
Collaborator

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

Description

This PR adds support for workspace in bruno

Contribution Checklist:

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

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

Publishing to New Package Managers

Please see here for more information.

Summary by CodeRabbit

  • New Features

    • Full workspace support: create/open/switch/rename/close workspaces with workspace-scoped collections, environments, and documentation.
    • Workspace home: new WorkspaceHome interface replaces the old welcome screen.
    • Import/open multiple collections and workspace-aware import/collection flows.
  • UI Changes

    • Collection action renamed from "Close" to "Remove" with updated confirmation and toast text.
    • Sidebar workspace selector and workspace-aware collection listing and search.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 2, 2025

Walkthrough

Adds multi-workspace support across renderer and main processes: Redux workspaces slice, workspace-aware collection/environment flows, Electron IPC and workspace watcher, WorkspaceHome UI replacing Welcome, many new workspace UI components, theme tokens, and extensive test updates for new modal/dropdown flows.

Changes

Cohort / File(s) Change Summary
Redux: workspaces slice & integration
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/*, packages/bruno-app/src/providers/ReduxStore/index.js
Added workspaces slice, actions and reducers for create/open/switch/rename/close, collection & environment membership, integrated into root reducer.
Collections actions (workspace-aware)
packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
Made create/open/import/remove collection flows workspace-aware (workspaceId options), added openMultipleCollections, adjusted removal to keep global collection until all workspaces drop it.
Global environments (workspace context)
packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js
Thunks now accept getState and include workspace context (uid/path) in IPC payloads; added internal getWorkspaceContext helper.
Electron: workspace IPC, watcher, stores
packages/bruno-electron/src/ipc/workspace.js, packages/bruno-electron/src/app/workspace-watcher.js, packages/bruno-electron/src/store/*, packages/bruno-electron/src/utils/workspace-config.js
New registerWorkspaceIpc handlers for full workspace lifecycle and environment management; WorkspaceWatcher for file watching; DefaultWorkspaceManager, LastOpenedWorkspaces, GlobalEnvironmentsManager and workspace-config utilities added.
Main process IPC changes & helpers
packages/bruno-electron/src/index.js, packages/bruno-electron/src/ipc/collection.js, packages/bruno-electron/src/ipc/global-environments.js, packages/bruno-electron/src/ipc/preferences.js, packages/bruno-electron/src/app/collections.js, packages/bruno-electron/src/utils/collection-import.js
Adjusted IPC registration signatures to use workspace managers; added renderer:open-multiple-collections; removed lastOpenedCollections dependency from several flows; added openCollectionsByPathname helper; updated import signatures.
Renderer: WorkspaceHome & related UI
packages/bruno-app/src/components/WorkspaceHome/*, packages/bruno-app/src/components/WorkspaceHome/WorkspaceCollections/*, packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/*
New WorkspaceHome component with tabs (Collections, Environments, Docs) and many subcomponents for collections, environments, docs, modals (create/import/rename/delete/copy/etc.).
Renderer: Sidebar / TitleBar / Workspace controls
packages/bruno-app/src/components/Sidebar/*, packages/bruno-app/src/components/Sidebar/TitleBar/*, packages/bruno-app/src/components/WorkspaceSidebar/CreateWorkspace/*
WorkspaceSelector, CloseWorkspace, CreateWorkspace components added; Collections component made workspace-aware and accepts showSearch; CreateCollection signature extended with defaultLocation; removal terminology changed (Close → Remove) across UI.
Welcome removal & default view change
packages/bruno-app/src/components/Welcome/*, packages/bruno-app/src/components/RequestTabPanel/index.js, packages/bruno-app/src/pages/Bruno/index.js
Removed Welcome component and its styles; RequestTabPanel and Bruno page now render WorkspaceHome when no tab is active.
Theme tokens
packages/bruno-app/src/themes/light.js, packages/bruno-app/src/themes/dark.js
Added listItem and workspace theme tokens (hover/active bg, workspace-specific color tokens).
Utilities: workspaces helpers
packages/bruno-app/src/utils/workspaces/index.js
Added sortWorkspaces, toggleWorkspacePin, reorderWorkspaces utilities for pinned/unpinned ordering and drag-reorder behavior.
IPC event listeners (renderer)
packages/bruno-app/src/providers/App/useIpcEvents.js
Added listeners for main:workspace-opened, main:workspace-config-updated and workspace-scoped global-environment events to dispatch Redux updates.
Tests & test utils updates
tests/**/*, tests/utils/page/*
Updated 40+ tests: replace single "Import/Create Collection" buttons with plus-icon → dropdown flows, use data-testid scoped modal locators, conditional Location input handling, and "Close" → "Remove" terminology; added/modified helpers (closeAllCollections, locators).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Renderer as App (renderer)
    participant Main as Main Process
    participant FS as File System

    User->>Renderer: createWorkspace (UI)
    Renderer->>Main: renderer:create-workspace (IPC)
    Main->>FS: create workspace dir & write workspace.yml
    Main->>Main: update last-opened store
    Main-->>Renderer: return workspace config
    Renderer->>Renderer: dispatch createWorkspaceAction -> update Redux
    Renderer->>User: show success & switch to workspace
Loading
sequenceDiagram
    participant User
    participant Renderer as App (renderer)
    participant Main as Main Process
    participant FS as File System

    User->>Renderer: createCollection (with workspaceId)
    Renderer->>Main: renderer:create-collection (IPC, workspacePath)
    Main->>FS: create collection folder & files
    Main->>FS: update workspace.yml (add collection)
    Main-->>Renderer: return collectionPath
    Renderer->>Renderer: dispatch createCollection -> add to workspace in Redux
    Renderer->>User: render collection in WorkspaceHome/Sidebar
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

  • Areas needing careful review:
    • Redux workspaces slice and related actions (state invariants when switching/removing workspaces and collections)
    • Electron IPC workspace handlers, file I/O, and watcher lifecycle (error handling, race conditions)
    • Migration logic in DefaultWorkspaceManager and LastOpenedWorkspaces (data integrity)
    • Collection/environments workspace-scoped IPC integration (ensure workspacePath/uid consistently propagated)
    • Large UI components (WorkspaceHome, EnvironmentList, WorkspaceCollections) with many modals and async flows
    • Test updates touching many suites — run representative tests for modal/dropdown interactions

Possibly related PRs

Suggested reviewers

  • lohit-bruno
  • helloanoop

Poem

🌱 Workspaces planted, folders rearranged,
Collections now know where they've changed,
IPC hums and Redux keeps score,
Welcome waved — WorkspaceHome opens the door,
Tests rebooted, themes tuned, new UI range.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'init: workspaces' directly describes the main feature addition in the PR—workspace support initialization across multiple components and services.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@naman-bruno
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 2, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (1)
packages/bruno-electron/src/ipc/preferences.js (1)

24-24: Minor typo: "environements".

Line 24 has a typo in the error message.

-      console.error("Error occured while fetching global environements!");
+      console.error("Error occurred while fetching global environments!");
🟡 Minor comments (16)
packages/bruno-electron/src/store/default-workspace.js-192-194 (1)

192-194: Silent migration failure may hide critical issues.

The catch block logs the error but doesn't propagate it. If migration fails, the workspace will be created without the user's collections and environments, potentially causing confusion. Consider at minimum warning the user or tracking partial migration state.

packages/bruno-app/src/components/WorkspaceOverview/WorkspaceEnvironments/EnvironmentList/ConfirmSwitchEnv.js-6-37 (1)

6-37: Consider the user experience for unsaved changes.

This modal warns about unsaved changes but only provides a "Close" button. Typically, unsaved changes modals offer the user choices like "Save and Continue", "Discard Changes", or "Cancel". The current implementation only allows dismissing the warning without addressing the unsaved changes, which may lead to accidental data loss.

Consider revising the modal to include actions that let the user:

  • Save the changes before switching
  • Discard the changes and proceed
  • Cancel and stay on the current environment
packages/bruno-app/src/components/WorkspaceOverview/WorkspaceEnvironments/CopyEnvironment/index.js-15-23 (1)

15-23: Handle long environment names to prevent validation errors.

If the base environment name is 44 characters or longer, appending " - Copy" (7 chars) will exceed the 50-character max and cause an immediate validation error when the modal opens. Consider truncating the base name or using a smarter default.

     initialValues: {
-      name: environment.name + ' - Copy'
+      name: (environment.name.length > 43 
+        ? environment.name.substring(0, 43) 
+        : environment.name) + ' - Copy'
     },
packages/bruno-app/src/components/Sidebar/TitleBar/CloseWorkspace/index.js-26-28 (1)

26-28: Duplicate success toast.

Per the closeWorkspaceAction implementation (see relevant snippet), it already calls toast.success('Workspace closed successfully') on success. Line 27 adds another toast.success('Workspace closed'), resulting in two toast notifications.

Remove the redundant toast:

       await dispatch(closeWorkspaceAction(workspace.uid));
-      toast.success('Workspace closed');
       onClose();
packages/bruno-app/src/components/WorkspaceSidebar/CreateWorkspace/index.js-89-107 (1)

89-107: Accessibility issue: htmlFor doesn't match input id.

The label's htmlFor="workspaceName" doesn't match the input's id="workspace-name". This breaks the label-input association for accessibility tools.

-              <label htmlFor="workspaceName" className="block font-semibold mb-2">
+              <label htmlFor="workspace-name" className="block font-semibold mb-2">
packages/bruno-app/src/components/WorkspaceSidebar/CreateWorkspace/index.js-111-117 (1)

111-117: Same accessibility issue for location label.

The htmlFor="workspaceLocation" doesn't match id="workspace-location".

-              <label htmlFor="workspaceLocation" className="block font-semibold mb-2">
+              <label htmlFor="workspace-location" className="block font-semibold mb-2">
packages/bruno-app/src/components/WorkspaceOverview/WorkspaceEnvironments/RenameEnvironment/index.js-76-91 (1)

76-91: Mismatched htmlFor and id attributes.

The label's htmlFor="name" doesn't match the input's id="environment-name", breaking the label-input association for accessibility.

-          <label htmlFor="name" className="block font-semibold">
+          <label htmlFor="environment-name" className="block font-semibold">
             Environment Name
           </label>
packages/bruno-app/src/utils/workspaces/index.js-53-62 (1)

53-62: Inconsistent null-safety for preferences.workspaces.

Line 57 spreads preferences.workspaces without a guard, but line 67 (else branch) uses (preferences?.workspaces || {}). If preferences.workspaces is undefined and the workspace is already pinned, this will throw.

   if (isPinned) {
     return {
       ...preferences,
       workspaces: {
-        ...preferences.workspaces,
+        ...(preferences?.workspaces || {}),
         pinnedWorkspaceUids: pinnedUids.filter((uid) => uid !== workspaceUid),
         pinnedOrder: pinnedOrder.filter((uid) => uid !== workspaceUid),
         unpinnedOrder: [...unpinnedOrder, workspaceUid]
       }
     };
packages/bruno-app/src/components/WorkspaceOverview/WorkspaceEnvironments/EnvironmentList/index.js-261-265 (1)

261-265: handleConfirmSwitch appears incomplete.

When saveChanges is true, nothing happens. If this is intentional (e.g., save logic handled elsewhere), add a comment. Otherwise, implement the save path.

 const handleConfirmSwitch = (saveChanges) => {
-  if (!saveChanges) {
-    setSwitchEnvConfirmClose(false);
-  }
+  setSwitchEnvConfirmClose(false);
+  if (saveChanges) {
+    // TODO: Implement save before switching
+  }
 };
packages/bruno-app/src/components/Sidebar/TitleBar/index.js-83-85 (1)

83-85: Missing error handling for non-workspace import.

The workspace branch has .catch() error handling, but the non-workspace importCollection dispatch does not. Add consistent error handling:

     } else {
-      dispatch(importCollection(collectionsToImport, collectionLocation));
+      dispatch(importCollection(collectionsToImport, collectionLocation))
+        .catch((err) => {
+          console.error(err);
+          toast.error('An error occurred while importing the collection');
+        });
     }
packages/bruno-electron/src/ipc/global-environments.js-35-46 (1)

35-46: Inconsistent return values between workspace and non-workspace paths.

When using workspaceEnvironmentsManager, results are returned to the caller. However, when using globalEnvironmentsStore, no value is returned (lines 41, 54, 67, 80). This inconsistency may cause issues if the renderer expects a response.

Consider returning a consistent result:

-      globalEnvironmentsStore.saveGlobalEnvironment({ environmentUid, variables });
+      globalEnvironmentsStore.saveGlobalEnvironment({ environmentUid, variables });
+      return true;
packages/bruno-electron/src/store/workspace-environments.js-294-317 (1)

294-317: Missing secrets cleanup on environment deletion.

When deleting an environment, associated secrets in environmentSecretsStore are not removed. This could leave orphaned secret data. Also, prefer async fs.promises.unlink:

  async deleteGlobalEnvironment(workspacePath, { environmentUid }) {
    try {
      // ...
      const envFile = this.findEnvironmentFileByUid(workspacePath, environmentUid);
      // ...
-     fs.unlinkSync(envFile.filePath);
+     await fs.promises.unlink(envFile.filePath);
+
+     // Clean up any associated secrets
+     const env = { name: envFile.name };
+     environmentSecretsStore.deleteEnvSecrets?.(workspacePath, env);
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js-17-17 (1)

17-17: Module-level ipcRenderer access may fail in non-browser contexts.

Extracting ipcRenderer at module level (line 17) will throw if window is undefined (e.g., during SSR or testing). Consider lazy access:

-const { ipcRenderer } = window;
+const getIpcRenderer = () => window.ipcRenderer;

Or access window.ipcRenderer directly in each function as done in other files.

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

packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js-404-413 (1)

404-413: action.name will be empty for arrow functions.

When calling handleWorkspaceAction with an inline arrow function (line 425), action.name is empty, resulting in a poor error message like "Failed to workspace":

-const handleWorkspaceAction = async (action, workspaceUid, ...args) => {
+const handleWorkspaceAction = async (action, actionName, ...args) => {
   try {
-    await action(workspaceUid, ...args);
+    await action(...args);
     return true;
   } catch (error) {
-    const actionName = action.name.replace('renderer:', '').replace('-', ' ');
     toast.error(error.message || `Failed to ${actionName} workspace`);
     throw error;
   }
 };

// Usage:
-await handleWorkspaceAction((...args) => ipcRenderer.invoke('renderer:rename-workspace', ...args),
-  workspace.pathname,
-  newName);
+await handleWorkspaceAction(
+  (...args) => ipcRenderer.invoke('renderer:rename-workspace', ...args),
+  'rename',
+  workspace.pathname,
+  newName
+);

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

packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js-2327-2337 (1)

2327-2337: Error message swallowed in openMultipleCollections.

The catch block rejects without the error, losing diagnostic information:

       .catch((err) => {
-        reject();
+        reject(err);
       });
packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js-2519-2560 (1)

2519-2560: Multiple async promise executors in utility functions.

Lines 2522, 2532, 2541, 2551 all use async promise executors unnecessarily. These can be simplified:

 export const loadRequestViaWorker =
   ({ collectionUid, pathname }) =>
-    (dispatch, getState) => {
-      return new Promise(async (resolve, reject) => {
-        const { ipcRenderer } = window;
-        ipcRenderer.invoke('renderer:load-request-via-worker', { collectionUid, pathname }).then(resolve).catch(reject);
-      });
+    () => {
+      const { ipcRenderer } = window;
+      return ipcRenderer.invoke('renderer:load-request-via-worker', { collectionUid, pathname });
     };

The async keyword in the executor serves no purpose since you're already using .then()/.catch().

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

🧹 Nitpick comments (64)
packages/bruno-electron/src/app/onboarding.js (2)

27-30: Update JSDoc to reflect parameter changes.

The function signature no longer includes lastOpenedCollections, but the JSDoc comment doesn't document the parameters at all. Consider adding parameter documentation for clarity.

Apply this diff to enhance the JSDoc:

 /**
  * Import sample collection for new users
+ * @param {string} collectionLocation - Directory path where the collection will be imported
+ * @param {Object} mainWindow - Electron BrowserWindow instance for IPC communication
+ * @returns {Promise<{collectionPath: string, uid: string, brunoConfig: Object}>}
  */
 async function importSampleCollection(collectionLocation, mainWindow) {

82-83: Simplify comments for better readability.

The comments are overly verbose and spread across two lines. The explanation can be more concise.

Apply this diff to condense the comment:

-      // lastOpenedCollections is still used here to check for existing collections during migration
-      const collections = lastOpenedCollections ? lastOpenedCollections.getAll() : [];
+      // Check for existing collections (supports users migrating from older versions)
+      const collections = lastOpenedCollections ? lastOpenedCollections.getAll() : [];
packages/bruno-app/src/themes/dark.js (1)

151-182: New listItem and workspace tokens look consistent; consider future color deduping

The new listItem and workspace branches are well‑named, mirror existing sidebar semantics, and fit the current dark palette (reusing #2A2D2F, #3D3D3D, and the existing accent system). That should make wiring up the new workspace surfaces straightforward.

If you touch theming again later, it might be worth centralizing some of the repeated hexes (#2A2D2F, #3D3D3D, #444, etc.) into colors to reduce magic values and keep light/dark parity easier to manage, but this is non‑blocking for this PR.

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

154-185: Good light/dark parity for listItem and workspace; optional follow‑up on palette reuse

The added listItem and workspace keys mirror the dark theme structure and reuse the existing orange accent + gray scale, which keeps workspace styling coherent across modes and should make component code simple (same token paths per theme).

Similar to the dark theme, if you later refactor theming it could help to pull #e7e7e7/#f3f3f3/#fbfbfb into the shared colors map so workspace and sidebar surfaces stay in lockstep, but this is a nice‑to‑have, not a blocker.

packages/bruno-electron/src/store/default-workspace.js (2)

139-140: Consider hoisting require statements to module level.

Inline require calls inside methods add overhead on each invocation. Since electron-store is already used elsewhere in the project via imported stores, consider either reusing those patterns or moving the require to the top of the file.


183-184: Inconsistent store access pattern.

Line 183 creates a new Store({ name: 'global-environments' }) to clear it, but you've already imported globalEnvironmentsStore at the top. Consider using a clear method on the imported store for consistency, or verify the imported store exposes this capability.

-        const globalEnvStore = new Store({ name: 'global-environments' });
-        globalEnvStore.clear();
+        globalEnvironmentsStore.clear();
packages/bruno-electron/src/app/collections.js (1)

135-146: Consider parallel processing for better performance.

The sequential await in the loop means collections are opened one at a time. For multiple collections, this could be noticeably slow. Consider using Promise.allSettled() to open collections in parallel, similar to the pattern used in openCollectionDialog at lines 77-91.

Additionally, errors are only logged to console without user notification. Consider collecting errors and notifying the user via win.webContents.send('main:display-error', ...) as done in openCollectionDialog.

Example refactor:

 const openCollectionsByPathname = async (win, watcher, collectionPaths, options = {}) => {
-  for (const collectionPath of collectionPaths) {
+  const promises = collectionPaths.map((collectionPath) => {
     const resolvedPath = path.isAbsolute(collectionPath)
       ? collectionPath
       : normalizeAndResolvePath(collectionPath);
     if (isDirectory(resolvedPath)) {
-      await openCollection(win, watcher, resolvedPath, options);
+      return openCollection(win, watcher, resolvedPath, options).catch((err) => {
+        console.error(`Cannot open collection at "${resolvedPath}":`, err.message);
+        return { error: err, path: resolvedPath };
+      });
     } else {
       console.error(`Cannot open unknown folder: "${resolvedPath}"`);
+      return { error: new Error('Not a directory'), path: resolvedPath };
     }
-  }
+  });
+  const results = await Promise.allSettled(promises);
+  const errors = results.filter((r) => r.status === 'rejected' || r.value?.error);
+  if (errors.length > 0 && !options.dontSendDisplayErrors) {
+    const paths = errors.map((e) => e.value?.path || 'unknown').join(', ');
+    win.webContents.send('main:display-error', `Some collections could not be opened: ${paths}`);
+  }
 };
packages/bruno-app/src/components/WorkspaceOverview/WorkspaceEnvironments/EnvironmentList/ConfirmSwitchEnv.js (1)

27-33: Remove the empty div.

The empty <div> at line 33 serves no purpose and can be removed for cleaner code.

       <div className="flex justify-between mt-6">
         <div>
           <button className="btn btn-sm btn-danger" onClick={onCancel}>
             Close
           </button>
         </div>
-        <div></div>
       </div>
packages/bruno-app/src/components/WorkspaceOverview/WorkspaceEnvironments/CopyEnvironment/index.js (1)

24-34: Update toast message to reflect copying action.

The success toast says "Environment created!" but since this is a copy operation, it should say "Environment copied!" for clarity.

       .then(() => {
-        toast.success('Environment created!');
+        toast.success('Environment copied!');
         onClose();
       })
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/index.js (1)

39-45: Consider logging when workspace is not found.

The updateWorkspace reducer silently does nothing if the workspace doesn't exist. While this may be intentional for resilience, consider adding a console warning to aid debugging, especially during development.

     updateWorkspace: (state, action) => {
       const { uid, ...updates } = action.payload;
       const workspace = state.workspaces.find((w) => w.uid === uid);
       if (workspace) {
         Object.assign(workspace, updates);
+      } else {
+        console.warn(`Attempted to update non-existent workspace: ${uid}`);
       }
     },
packages/bruno-app/src/components/Sidebar/TitleBar/StyledWrapper.js (2)

127-130: Simplify theme fallback chain.

The deep fallback chain with a hardcoded color makes theme maintenance difficult. Consider using a single theme path or defining a default in the theme definition.

     .check-icon {
-      color: ${(props) => props.theme.workspace?.accent || props.theme.colors?.text?.yellow || '#f0c674'};
+      color: ${(props) => props.theme.workspace.accent};
       flex-shrink: 0;
     }

132-155: Simplify theme fallback chains for pin button.

Multiple fallback chains with hardcoded colors on lines 143-144 and 152-153 reduce maintainability.

     .pin-btn {
       display: flex;
       align-items: center;
       justify-content: center;
       width: 22px;
       height: 22px;
       padding: 0;
       border: none;
       background: transparent;
       border-radius: 4px;
       cursor: pointer;
-      color: ${(props) => props.theme.dropdown?.mutedText || props.theme.text?.muted || '#888'};
+      color: ${(props) => props.theme.text.muted};
       transition: background 0.15s ease, color 0.15s ease, opacity 0.15s ease;
       opacity: 0;

       &.pinned {
         opacity: 1;
       }

       &:hover {
-        background: ${(props) => props.theme.dropdown?.hoverBg || props.theme.sidebar?.collection?.item?.hoverBg};
-        color: ${(props) => props.theme.dropdown?.mutedText || props.theme.text?.muted || '#888'};
+        background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
+        color: ${(props) => props.theme.text.muted};
       }
     }
packages/bruno-app/src/components/WorkspaceOverview/WorkspaceEnvironments/DeleteEnvironment/index.js (1)

11-18: Consider logging error details and adding loading state.

While the error handling structure is correct, the error details are discarded on line 17. Logging these details would aid debugging. Additionally, there's no loading/disabled state during the deletion operation.

   const onConfirm = () => {
     dispatch(deleteGlobalEnvironment({ environmentUid: environment.uid }))
       .then(() => {
         toast.success('Environment deleted successfully');
         onClose();
       })
-      .catch(() => toast.error('An error occurred while deleting the environment'));
+      .catch((error) => {
+        console.error('Error deleting environment:', error);
+        toast.error(error?.message || 'An error occurred while deleting the environment');
+      });
   };
packages/bruno-app/src/components/WorkspaceOverview/WorkspaceEnvironments/DeleteEnvironment/StyledWrapper.js (1)

3-13: Investigate why !important is required.

Lines 6, 7, and 10 use !important flags, which typically indicates specificity conflicts. The border: inherit !important is especially unusual since inherit rarely requires forcing. Consider reviewing the Modal component's default styles to apply these rules without !important.

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

96-112: Inconsistent theming approach for tab colors.

Line 99 uses a CSS variable var(--color-tab-inactive) while the rest of the file uses props.theme.*. This inconsistency makes theme management harder. Consider using ${(props) => props.theme.tabs.inactive.color} for consistency.

   .tab-item {
     position: relative;
     cursor: pointer;
-    color: var(--color-tab-inactive);
+    color: ${(props) => props.theme.tabs.inactive.color};
     border-bottom: 2px solid transparent;
     transition: all 0.15s ease;

     &:hover {
       color: ${(props) => props.theme.text.primary};
       border-bottom-color: ${(props) => props.theme.colors.border};
     }

     &.active {
       border-bottom-color: ${(props) => props.theme.colors.text.yellow};
       color: ${(props) => props.theme.tabs.active.color};
     }
   }
packages/bruno-electron/src/ipc/preferences.js (1)

5-5: Unused watcher parameter.

The watcher parameter is declared but never used within the function body. If it's no longer needed after removing the lastOpenedCollections logic, consider removing it from the signature.

-const registerPreferencesIpc = (mainWindow, watcher) => {
+const registerPreferencesIpc = (mainWindow) => {

Note: This would require updating the call site in packages/bruno-electron/src/index.js.

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

84-92: Unusual height styling.

The h-[1px] combined with min-h-[500px] (line 85) is a peculiar pattern. The element's natural height would be 1px, only expanding due to min-height. This could cause layout issues if content exceeds 500px.

Consider using h-full or min-h-full instead for more predictable behavior:

-          <div className="h-[1px] min-h-[500px]">
+          <div className="h-full min-h-[500px]">
packages/bruno-app/src/components/WorkspaceOverview/WorkspaceCollections/StyledWrapper.js (1)

1-271: Well-structured styled-component with good use of modern CSS features.

The CSS organization is clean with clear class naming and semantic structure. The :has() selectors elegantly handle conditional grid layouts based on git column presence.

Note: The hardcoded hex colors (#3B82F6, #10B981, #EF4444, etc.) appear consistent with patterns elsewhere in the codebase. If theme color variables exist for these values, consider extracting them for maintainability, though the current approach is acceptable.

The :has() selector is supported in Chromium 105+, and modern Electron versions (24+) ship with Chromium 112+, so compatibility should not be a concern.

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

190-267: Duplicate styling between .rename-container and &.creating blocks.

The .environment-name-input and .inline-actions styles are defined identically in both .rename-container (lines 195-213) and &.creating (lines 217-235). Consider consolidating these into a shared selector.

packages/bruno-app/src/components/WorkspaceOverview/WorkspaceEnvironments/ImportEnvironment/index.js (1)

18-19: Simplify filter condition.

The parentheses around the filter callback condition are inconsistent with the coding guidelines (single-line conditions don't need wrapping).

-          .filter((env) =>
-            env.name && env.name !== 'undefined')
+          .filter((env) => env.name && env.name !== 'undefined')
packages/bruno-app/src/components/WorkspaceSidebar/CreateWorkspace/index.js (1)

68-72: useEffect dependency array should be empty.

The inputRef is a ref object that doesn't change between renders. Including it in the dependency array is unnecessary. Use an empty array for a mount-only effect.

   useEffect(() => {
     if (inputRef && inputRef.current) {
       inputRef.current.focus();
     }
-  }, [inputRef]);
+  }, []);
packages/bruno-app/src/components/WorkspaceOverview/index.js (3)

41-54: Stale closure risk in click-outside handler.

The handleCancelWorkspaceRename function is referenced inside the effect but not included in the dependency array. If the handler's closure captures stale state in the future, this could cause bugs.

   useEffect(() => {
     if (!isRenamingWorkspace) return;

     const handleClickOutside = (event) => {
       if (workspaceRenameContainerRef.current && !workspaceRenameContainerRef.current.contains(event.target)) {
         handleCancelWorkspaceRename();
       }
     };

     document.addEventListener('mousedown', handleClickOutside);
     return () => {
       document.removeEventListener('mousedown', handleClickOutside);
     };
-  }, [isRenamingWorkspace]);
+  }, [isRenamingWorkspace, handleCancelWorkspaceRename]);

Wrap handleCancelWorkspaceRename in useCallback to stabilize its reference if you add it to the dependency array.


123-137: Redundant validation check.

Line 128-130 (name.length < 1) is redundant since the check at line 124 already handles empty or whitespace-only strings.

 const validateWorkspaceName = (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';
   }

   return null;
 };

184-186: Dead code: duplicate null check.

This null check is unreachable since the component already returns null at lines 56-58 when activeWorkspace is falsy.

-  if (!activeWorkspace) {
-    return null;
-  }
-
   const tabs = [
packages/bruno-app/src/components/WorkspaceOverview/WorkspaceEnvironments/RenameEnvironment/index.js (3)

7-10: Consolidate duplicate imports.

useDispatch and useSelector can be imported in a single statement from react-redux.

-import { useDispatch } from 'react-redux';
 import { renameGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
 import { validateName, validateNameError } from 'utils/common/regex';
-import { useSelector } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';

39-52: Silent no-op when name is unchanged.

If the user clicks "Rename" without changing the name, the form silently does nothing and the modal stays open. Consider closing the modal or providing feedback.

     onSubmit: (values) => {
       if (values.name === environment.name) {
+        onClose();
         return;
       }

55-59: Simplify useEffect dependency.

The inputRef reference is stable and won't change. The dependency array can be empty since the effect only needs to run once on mount.

   useEffect(() => {
     if (inputRef && inputRef.current) {
       inputRef.current.focus();
     }
-  }, [inputRef]);
+  }, []);
packages/bruno-electron/src/store/last-opened-workspaces.js (1)

25-31: Redundant property assignment.

name: workspaceConfig.name on line 28 is redundant since workspaceConfig is already spread. Only override if you intend to transform the value.

     const workspaceEntry = {
       ...workspaceConfig,
       uid: workspaceUid,
-      name: workspaceConfig.name,
       lastOpened: new Date().toISOString(),
       pathname: workspacePath
     };
packages/bruno-app/src/components/WorkspaceOverview/WorkspaceCollections/index.js (2)

235-260: Inconsistent terminology: "Delete" vs "Remove".

The modal title and confirm button say "Delete Collection" but the action button (line 335) says "Remove from workspace". For git-backed collections, the action only removes the reference, not the files. Consider aligning the terminology or making it context-dependent.

The explanation text at lines 249-258 is good, but the modal title/button could be more precise:

         <Modal
           size="sm"
-          title="Delete Collection"
+          title="Remove Collection"
           handleCancel={() => setCollectionToRemove(null)}
           handleConfirm={confirmRemoveCollection}
-          confirmText="Delete Collection"
+          confirmText="Remove"
           cancelText="Cancel"

48-91: Unnecessary dependency in useMemo.

workspace.pathname is included in the dependency array but isn't used in the memo computation. This could cause unnecessary recalculations.

-  }, [workspace.collections, collections, workspace.pathname]);
+  }, [workspace.collections, collections]);
packages/bruno-app/src/components/WorkspaceOverview/WorkspaceEnvironments/CreateEnvironment/index.js (3)

51-55: Unnecessary dependency in useEffect.

The inputRef object reference is stable across renders. Including it in the dependency array is harmless but misleading. An empty dependency array [] is more idiomatic for "run once on mount."

   useEffect(() => {
     if (inputRef && inputRef.current) {
       inputRef.current.focus();
     }
-  }, [inputRef]);
+  }, []);

39-45: Callback invoked after modal closes—potential race condition.

onEnvironmentCreated is called after onClose(). If onClose unmounts this component or resets parent state, the callback may execute on stale context. Consider calling the callback before closing, or consolidating into a single parent handler.

       dispatch(addGlobalEnvironment({ name: values.name }))
         .then(() => {
           toast.success('Environment created!');
-          onClose();
-          // Call the callback if provided
           if (onEnvironmentCreated) {
             onEnvironmentCreated();
           }
+          onClose();
         })

21-22: enableReinitialize is unnecessary for static initial values.

Since initialValues is always { name: '' } and doesn't depend on props, enableReinitialize: true adds no value here.

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

22-37: DefaultTab uses CSS class instead of SharedButton component.

DefaultTab uses className="shared-button" on plain <button> elements, but there's an exported SharedButton component above. Consider using SharedButton for consistency, or clarify if the CSS class is intentionally different from the component.

packages/bruno-app/src/components/WorkspaceOverview/WorkspaceEnvironments/EnvironmentList/index.js (3)

54-57: Redundant condition in selectedEnvironment sync.

The check hasSelectedEnvironmentChanged || selectedEnvironment.uid !== _selectedEnvironment?.uid is redundant. If UIDs differ, isEqual would already return false, making hasSelectedEnvironmentChanged true.

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

144-161: Consider memoizing validateEnvironmentName.

This function is defined inside the component body and recreated on every render. Since it references globalEnvs from Redux, wrapping it with useCallback and including globalEnvs as a dependency would be more efficient, especially if passed to child components.


36-66: Complex useEffect with multiple fallback paths.

The selectedEnvironment synchronization logic has multiple fallback scenarios (by uid, by name, by activeEnvironmentUid, then first). This is comprehensive but could benefit from inline comments explaining each fallback to aid future maintainers.

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

84-86: Silent no-op for cross-group drag.

When dragging between pinned and unpinned groups, the function returns unchanged preferences without feedback. This may confuse callers expecting an error or state change. Consider returning a distinct value or logging.

packages/bruno-electron/src/ipc/workspace.js (3)

189-194: Direct store access breaks encapsulation.

Line 193 directly accesses lastOpenedWorkspaces.store.set(...) instead of using a method on LastOpenedWorkspaces. Consider adding an update or updateName method to maintain encapsulation.


69-71: Redundant try/catch blocks that only rethrow.

Multiple handlers have catch (error) { throw error; } which adds no value. Either handle the error meaningfully (e.g., wrap with context) or remove the try/catch.

Also applies to: 96-98, 132-134


402-431: Error handling returns null—may hide issues.

renderer:get-default-workspace catches all errors and returns null. This silently hides problems like permission errors or corrupted YAML. Consider logging the error or returning an error object.

     } catch (error) {
+      console.error('Failed to get default workspace:', error.message);
       return null;
     }
packages/bruno-app/src/components/Sidebar/Collections/index.js (2)

19-35: Simplify duplicated workspace collection mapping logic.

Both branches (default workspace and non-default) perform the identical mapping operation. This can be consolidated:

- if (!activeWorkspace || activeWorkspace.type === 'default') {
-   if (activeWorkspace && activeWorkspace.collections && activeWorkspace.collections.length > 0) {
-     allCollections = activeWorkspace.collections.map((wc) => {
-       const loadedCollection = collections.find((c) => c.pathname === wc.path);
-       return loadedCollection;
-     }).filter(Boolean);
-   } else {
-     allCollections = [];
-   }
- } else {
-   if (activeWorkspace.collections && activeWorkspace.collections.length > 0) {
-     allCollections = activeWorkspace.collections.map((wc) => {
-       const loadedCollection = collections.find((c) => c.pathname === wc.path);
-       return loadedCollection;
-     }).filter(Boolean);
-   }
- }
+ if (activeWorkspace?.collections?.length > 0) {
+   allCollections = activeWorkspace.collections
+     .map((wc) => collections.find((c) => c.pathname === wc.path))
+     .filter(Boolean);
+ }

91-99: Redundant null/length check.

allCollections is guaranteed to have items here since the early return on line 37 handles the empty case. Simplify:

- {allCollections && allCollections.length
-   ? allCollections.map((c) => {
-       return (
-         <Collection searchText={searchText} collection={c} key={c.uid} />
-       );
-     })
-   : null}
+ {allCollections.map((c) => (
+   <Collection searchText={searchText} collection={c} key={c.uid} />
+ ))}
packages/bruno-app/src/components/Sidebar/TitleBar/index.js (3)

75-75: Inconsistent indentation.

Line 75 uses 8 spaces which breaks from the surrounding code pattern. Should align with the rest of the function.

-        setImportCollectionLocationModalOpen(false);
+    setImportCollectionLocationModalOpen(false);

95-102: Move WorkspaceName outside component to avoid recreation on every render.

Defining a forwardRef component inside TitleBar causes it to be recreated on every render, potentially causing unnecessary DOM reconciliation. Extract it outside or memoize it.

+const WorkspaceName = forwardRef(({ name, onClick }, ref) => {
+  return (
+    <div ref={ref} className="workspace-name-container" onClick={onClick}>
+      <span className="workspace-name">{name}</span>
+      <IconChevronDown size={14} stroke={1.5} className="chevron-icon" />
+    </div>
+  );
+});

const TitleBar = ({ showSearch, setShowSearch }) => {
  // ... later use:
  // <WorkspaceName 
  //   ref={ref} 
  //   name={toTitleCase(activeWorkspace?.name) || 'Default Workspace'} 
  //   onClick={() => setShowWorkspaceDropdown(!showWorkspaceDropdown)} 
  // />

40-47: Extract toTitleCase as a utility function.

This is a pure function that doesn't depend on component state or props. Move it outside the component to avoid recreation on each render and improve reusability.

packages/bruno-electron/src/ipc/global-environments.js (1)

7-7: Unused workspaceUid parameter.

workspaceUid is destructured but never used in the handler logic. Either remove it from the destructuring or clarify if it's intended for future use.

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

153-178: Redundant validation check.

Lines 158-160 check name.length < 1, but this condition is already covered by lines 154-156 checking for empty/whitespace names. Remove the redundant check:

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

-    if (name.length < 1) {
-      return 'Must be at least 1 character';
-    }
-
     if (name.length > 255) {
packages/bruno-electron/src/store/workspace-environments.js (2)

57-83: Mixing synchronous and asynchronous file operations.

parseEnvironmentFile is async but uses fs.readFileSync. For consistency and to avoid blocking the event loop, consider using fs.promises.readFile:

  async parseEnvironmentFile(filePath, workspacePath) {
-   const content = fs.readFileSync(filePath, 'utf8');
+   const content = await fs.promises.readFile(filePath, 'utf8');
    const environment = await parseEnvironment(content, { format: 'yml' });

Same applies to fs.readdirSync in getGlobalEnvironments and findEnvironmentFileByUid.


332-354: Consider documenting the purpose of path-based wrapper methods.

These thin wrappers (getGlobalEnvironmentsByPath, addGlobalEnvironmentByPath, etc.) currently just delegate to their counterparts. A brief JSDoc comment explaining the API design intent would help maintainability.

packages/bruno-electron/src/ipc/collection.js (2)

91-97: Missing semicolon at end of function.

Per coding guidelines, always add semicolons at the end of statements.

 const validatePathIsInsideCollection = (filePath) => {
   const collectionPath = findCollectionPathByItemPath(filePath);

   if (!collectionPath) {
     throw new Error(`Path: ${filePath} should be inside a collection`);
   }
-}
+};

818-825: Inline require and magic string 'default' for workspace type check.

  1. The require at line 820 should be moved to the top of the file with other imports.
  2. The string literal 'default' for workspace type comparison could be fragile—consider using a constant.
+const { removeCollectionFromWorkspace } = require('../utils/workspace-config');
+const DEFAULT_WORKSPACE = 'default';
 // ... at top of file
 
-    if (workspacePath && workspacePath !== 'default') {
+    if (workspacePath && workspacePath !== DEFAULT_WORKSPACE) {
       try {
-        const { removeCollectionFromWorkspace } = require('../utils/workspace-config');
         await removeCollectionFromWorkspace(workspacePath, collectionPath);
       } catch (error) {
packages/bruno-electron/src/app/workspace-watcher.js (2)

16-19: Duplicate envHasSecrets function.

This function is duplicated from collection.js (line 69-73). Consider extracting to a shared utility.


50-65: Hardcoded .yml extension removal could be fragile.

Consider using path.parse() or path.basename(pathname, '.yml') for safer extension handling.

 const parseGlobalEnvironmentFile = async (pathname, workspacePath, workspaceUid) => {
   const basename = path.basename(pathname);
-  const environmentName = basename.slice(0, -'.yml'.length);
+  const environmentName = path.basename(pathname, '.yml');
packages/bruno-app/src/components/Sidebar/CreateCollection/index.js (1)

108-110: Silent error handling in browse.

The catch block swallows errors silently. If this is for handling user cancellation, consider checking the error type. Otherwise, unexpected errors will be hidden.

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

17-17: Unused prop originalEnvironmentVariables.

The prop originalEnvironmentVariables is declared but never used in the component. Either remove it from the signature or use it where environment.variables is referenced.


47-63: Yup conditional validation with $isLastRow context won't trigger.

The when('$isLastRow', ...) syntax requires passing context via Formik's validationContext. Since no context is provided, this conditional never activates. The custom validate function at lines 64-85 handles this instead, making this schema logic dead code.

Consider simplifying by removing the unused Yup 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()
     })),

Note: The static analysis warning about then on line 52 is a false positive—it's a Yup schema method, not a Promise property.


164-168: setTimeout for adding new row is fragile.

Using setTimeout(..., 0) to sequence state updates is a common pattern but can lead to timing issues with rapid input. Consider using Formik's setValues to update both the current value and add the new row atomically.

packages/bruno-electron/src/utils/workspace-config.js (2)

20-33: Code duplication: normalizeCollectionEntry is not used in addCollectionToWorkspace.

addCollectionToWorkspace (lines 131-138) duplicates the normalization logic already present in normalizeCollectionEntry (lines 20-33). Consider reusing the helper:

 const addCollectionToWorkspace = async (workspacePath, collection) => {
   const config = readWorkspaceConfig(workspacePath);

   if (!config.collections) {
     config.collections = [];
   }

-  // Normalize collection entry
-  const normalizedCollection = {
-    name: collection.name,
-    path: collection.path
-  };
-
-  if (collection.remote) {
-    normalizedCollection.remote = collection.remote;
-  }
+  const normalizedCollection = normalizeCollectionEntry(workspacePath, collection);

Also applies to: 123-151


153-193: Consider extracting the filter predicate for readability.

The filter callback has side effects (setting removedCollection and shouldDeleteFiles). While functional, extracting the match logic could improve clarity:

+const matchesCollectionPath = (workspacePath, collectionPathFromYml, targetPath) => {
+  if (!collectionPathFromYml) return false;
+  const absolutePath = path.isAbsolute(collectionPathFromYml)
+    ? collectionPathFromYml
+    : path.resolve(workspacePath, collectionPathFromYml);
+  return path.normalize(absolutePath) === path.normalize(targetPath);
+};

This is optional but would make the intent clearer.

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

177-211: Parameter reassignment detected.

Line 188 reassigns environmentUid which shadows the original parameter. Use a separate variable:

-  let environment = globalEnvironments?.find((env) => env?.uid == environmentUid);
+  let resolvedEnvironmentUid = environmentUid;
+  let environment = globalEnvironments?.find((env) => env?.uid == resolvedEnvironmentUid);
   if (!environment) {
     const activeUid = state.globalEnvironments?.activeGlobalEnvironmentUid;
     const activeEnv = globalEnvironments?.find((env) => env?.uid == activeUid);
     if (activeEnv) {
       environment = activeEnv;
-      environmentUid = activeEnv.uid;
+      resolvedEnvironmentUid = activeEnv.uid;
     }
   }
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js (3)

158-181: Unconventional pattern: using dispatch to access state.

Lines 165 and 168 use dispatch((_, getState) => getState()...) to read state. Consider passing getState to the helper:

-const loadWorkspaceCollectionsForSwitch = async (dispatch, workspace) => {
+const loadWorkspaceCollectionsForSwitch = async (dispatch, getState, workspace) => {
   // ...
-  const updatedWorkspace = await dispatch((_, getState) => getState().workspaces...);
+  const updatedWorkspace = getState().workspaces.workspaces.find((w) => w.uid === workspace.uid);

286-294: Silent catch block hides watcher startup failures.

Consider at minimum logging the error:

           try {
             await ipcRenderer.invoke('renderer:start-workspace-watcher', workspace.pathname);
           } catch (error) {
+            console.warn('Failed to start workspace watcher:', workspace.pathname, error);
           }

318-355: Multiple silent catch blocks in config update handler.

Lines 347 and 351-352 have empty catches. At minimum, log for debugging:

             try {
               await dispatch(openMultipleCollections(newCollectionPaths, { workspaceId: workspace.pathname }));
             } catch (error) {
+              console.error('Failed to open collections on config update:', error);
             }
           }
         }
       } catch (error) {
+        console.error('Failed to reload workspace collections:', error);
       }

Comment on lines +2364 to +2393
export const importCollection = (collection, collectionLocation, options = {}) => (dispatch, getState) => {
return new Promise(async (resolve, reject) => {
const { ipcRenderer } = window;

ipcRenderer.invoke('renderer:import-collection', collection, collectionLocation).then(resolve).catch(reject);
});
};
try {
const state = getState();
const activeWorkspace = state.workspaces.workspaces.find((w) => w.uid === state.workspaces.activeWorkspaceUid);

export const moveCollectionAndPersist =
({ draggedItem, targetItem }) =>
(dispatch, getState) => {
dispatch(moveCollection({ draggedItem, targetItem }));
const collectionPath = await ipcRenderer.invoke('renderer:import-collection', collection, collectionLocation);

return new Promise((resolve, reject) => {
const { ipcRenderer } = window;
const state = getState();
if (activeWorkspace && activeWorkspace.pathname && activeWorkspace.type !== 'default') {
const workspaceCollection = {
name: collection.name,
path: collectionPath
};

const collectionPaths = state.collections.collections.map((collection) => collection.pathname);
await ipcRenderer.invoke('renderer:add-collection-to-workspace', activeWorkspace.pathname, workspaceCollection);

ipcRenderer.invoke('renderer:update-collection-paths', collectionPaths).then(resolve).catch(reject);
});
};
dispatch(addCollectionToWorkspace({
workspaceUid: activeWorkspace.uid,
collection: workspaceCollection
}));
}

resolve(collectionPath);
} catch (error) {
reject(error);
}
});
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Async promise executor - convert to async/await.

Same issue as handleCollectionItemDrop. Convert to async thunk:

-export const importCollection = (collection, collectionLocation, options = {}) => (dispatch, getState) => {
-  return new Promise(async (resolve, reject) => {
+export const importCollection = (collection, collectionLocation, options = {}) => async (dispatch, getState) => {
     const { ipcRenderer } = window;

     try {
       // ... existing logic ...
-      resolve(collectionPath);
+      return collectionPath;
     } catch (error) {
-      reject(error);
+      throw error;
     }
-  });
 };
🧰 Tools
🪛 Biome (2.1.2)

[error] 2365-2392: Promise executor functions should not be async.

(lint/suspicious/noAsyncPromiseExecutor)

🤖 Prompt for AI Agents
In packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
around lines 2364 to 2393, the action currently wraps an async executor in new
Promise; replace it with an async thunk by making the outer function async
(export const importCollection = (collection, collectionLocation, options = {})
=> async (dispatch, getState) => { ... }), remove the explicit new Promise, keep
the try/catch but return collectionPath on success and re-throw the caught error
(or throw a new error) on failure so callers receive a rejected promise;
preserve the ipcRenderer.invoke calls, workspace lookup,
dispatch(addCollectionToWorkspace(...)) behavior and ensure you reference
ipcRenderer from window before using it.

Comment on lines +223 to +229
const hasProcessedCollections = workspace.collections
&& workspace.collections.length > 0
&& workspace.collections.some((c) => c.path && c.path.startsWith('/'));

if (!force && hasProcessedCollections) {
return workspace.collections;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential Windows path compatibility issue.

The check c.path.startsWith('/') only matches Unix absolute paths. Windows paths like C:\Users\... won't match:

-        && workspace.collections.some((c) => c.path && c.path.startsWith('/'));
+        && workspace.collections.some((c) => c.path && path.isAbsolute(c.path));

This requires importing path from the appropriate utility module.

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

🤖 Prompt for AI Agents
In packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js
around lines 223–229, the code checks c.path.startsWith('/') which only detects
POSIX absolute paths and will miss Windows absolute paths; update the check to
use a platform-aware absolute-path test (e.g., import the project's path utility
or Node's path and use path.isAbsolute(c.path) or an equivalent helper), ensure
c.path is a string before calling the helper, and replace the startsWith
condition with that isAbsolute check so both Windows and POSIX absolute paths
are detected.

@@ -2,8 +2,9 @@ import React, { useRef, useEffect, forwardRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
Copy link
Contributor

Choose a reason for hiding this comment

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

When I ran the code locally, clicking the options dropdown in Create Collection does nothing.

const isDefaultWorkspace = activeWorkspace?.type === 'default';

const shouldShowAccordion = workspaceUid && hideLocationInput && !isDefaultWorkspace;
const actuallyHideLocationInput = hideLocationInput && !showExternalLocation && !isDefaultWorkspace;
Copy link
Contributor

Choose a reason for hiding this comment

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

This logic is hard to read and decipher. Lets discuss and cleanup when we meet in person

<div className="flex justify-between items-center mt-8 bruno-modal-footer">
<div className="flex advanced-options">
<Dropdown onCreate={onDropdownCreate} icon={<AdvancedOptions />} placement="bottom-start">
{shouldShowAccordion && (
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see an Accordian based UI in the Create Collection component. We should be using some other term I feel.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

should i add "external location" option to a accordion or keep it in options?

@@ -0,0 +1,134 @@
import styled from 'styled-components';
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use theme props for border radius and font sizes throughout the PR

@@ -0,0 +1,271 @@
import styled from 'styled-components';
Copy link
Contributor

Choose a reason for hiding this comment

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

I see a bunch of unneeded styles in this PR. Please cleanup.

@coderabbitai coderabbitai bot mentioned this pull request Dec 29, 2025
6 tasks
@coderabbitai coderabbitai bot mentioned this pull request Jan 14, 2026
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants