Skip to content

Conversation

@naman-bruno
Copy link
Collaborator

@naman-bruno naman-bruno commented Dec 7, 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

Release Notes

  • New Features

    • Added API specification management capabilities: create, edit, and save YAML-based API specs within workspaces.
    • Integrated Swagger UI to preview and visualize API specifications.
    • Introduced code editor with YAML syntax highlighting for editing API spec files.
    • Added OpenAPI 3.0.0 export functionality to generate API specs from collections.
  • Dependencies

    • Added support libraries for YAML parsing, XML handling, and API documentation visualization.

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

@coderabbitai
Copy link

coderabbitai bot commented Dec 7, 2025

Walkthrough

Introduces comprehensive API specification support to Bruno, including frontend UI components for editing and rendering OpenAPI specs, Redux state management for API specs, Electron backend file handling with watchers, and integration with workspace and sidebar navigation systems.

Changes

Cohort / File(s) Change Summary
Dependencies
packages/bruno-app/package.json
Added runtime dependencies: js-yaml, xml2js, swagger-ui-react for YAML parsing, XML handling, and Swagger UI rendering.
API Spec Panel (Main Container)
packages/bruno-app/src/components/ApiSpecPanel/index.js, packages/bruno-app/src/components/ApiSpecPanel/StyledWrapper.js
New ApiSpecPanel container component with header, file editor, Swagger renderer, and dropdown menu for spec creation/opening. Includes theme-aware styling.
API Spec Editor
packages/bruno-app/src/components/ApiSpecPanel/FileEditor/index.js, packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/index.js, packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/StyledWrapper.js
New FileEditor and CodeEditor components providing CodeMirror-based YAML editing with save/edit callbacks, theme switching, and controlled value updates.
YAML Plugin & Syntax Highlighting
packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/Plugins/Yaml/index.js
New CodeMirror YAML plugin with syntax highlighting, token types, and indentation-based code folding.
Swagger Renderer
packages/bruno-app/src/components/ApiSpecPanel/Renderers/Swagger/index.js, packages/bruno-app/src/components/ApiSpecPanel/Renderers/Swagger/StyledWrapper.js
New Swagger component wrapping swagger-ui-react with theme-aware styling and dark-mode filters.
Sidebar API Specs
packages/bruno-app/src/components/Sidebar/ApiSpecs/index.js, packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecBadge/index.js, packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecItem/index.js, packages/bruno-app/src/components/Sidebar/ApiSpecs/StyledWrapper.js, packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecBadge/StyledWrapper.js
New API specs sidebar section with badge, item list, click handlers, and theme-aware styling for workspace-scoped spec management.
Sidebar Create/Close Modals
packages/bruno-app/src/components/Sidebar/ApiSpecs/CreateApiSpec/index.js, packages/bruno-app/src/components/Sidebar/ApiSpecs/CreateApiSpec/StyledWrapper.js, packages/bruno-app/src/components/Sidebar/ApiSpecs/CloseApiSpec/index.js
New modals for creating API specs (blank or from collection with environment support) and confirming spec closure. Includes Formik validation and IPC-driven folder selection.
Sidebar Navigation Integration
packages/bruno-app/src/components/Sidebar/Collections/index.js, packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js, packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js, packages/bruno-app/src/components/Sidebar/index.js
Integrated ApiSpecs into Collections sidebar, removed multi-view tab logic from SidebarHeader, added CreateApiSpec modal trigger, simplified header styling.
Main Page Integration
packages/bruno-app/src/pages/Bruno/index.js
Added conditional rendering of ApiSpecPanel when showApiSpecPage flag is active.
Redux API Spec Slice
packages/bruno-app/src/providers/ReduxStore/slices/apiSpec.js
New Redux slice managing apiSpecs array, activeApiSpecUid, and async thunks: openApiSpec, saveApiSpecToFile, createApiSpecFile, closeApiSpecFile.
Redux App Slice
packages/bruno-app/src/providers/ReduxStore/slices/app.js
Added showApiSpecPage and hideApiSpecPage reducers to toggle API spec panel visibility with mutual exclusivity.
Redux Workspace Actions
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js
New loadWorkspaceApiSpecs thunk to load and open API specs for a workspace; integrated into workspace switch and config update flows.
Redux Store Integration
packages/bruno-app/src/providers/ReduxStore/index.js
Wired apiSpecReducer into Redux store under apiSpec key.
IPC Events Provider
packages/bruno-app/src/providers/App/useIpcEvents.js
Added handling for main:apispec-tree-updated IPC events to dispatch apiSpecAddFileEvent and apiSpecChangeFileEvent actions.
API Spec Exporter
packages/bruno-app/src/utils/exporters/openapi-spec.js
New exportApiSpec utility generating OpenAPI 3.0.0 YAML from collection items, variables, and authentication schemes; supports multiple request body formats (JSON, XML, multipart, form-urlencoded) with error handling and warnings.
Electron API Specs Handler
packages/bruno-electron/src/app/apiSpecs.js
New openApiSpec and openApiSpecDialog functions for opening/managing API spec files, workspace.yml persistence, and frontend tree updates.
Electron API Specs Watcher
packages/bruno-electron/src/app/apiSpecsWatcher.js
New ApiSpecWatcher class using chokidar to monitor API spec files, emit tree updates on add/change, parse content based on extension (YAML/JSON).
Electron API Spec UIDs Cache
packages/bruno-electron/src/cache/apiSpecUids.js
New UID persistence utility with getApiSpecUid, moveApiSpecUid, removeApiSpecUid for stable spec identification across moves.
Electron IPC API Spec Handlers
packages/bruno-electron/src/ipc/apiSpec.js
New registerApiSpecIpc function wiring 7 IPC handlers: open-api-spec, open-api-spec-file, save-api-spec, create-api-spec, remove-api-spec, fetch-api-spec, ensure-apispec-folder.
Electron IPC Collection Updates
packages/bruno-electron/src/ipc/collection.js
Added renderer:get-collection-json IPC handler to extract collection data including environment variables and Bruno request files for API spec creation.
Electron IPC Workspace Handler
packages/bruno-electron/src/ipc/workspace.js
New renderer:load-workspace-apispecs IPC handler to read and resolve workspace API spec paths.
Electron Workspace Config Utilities
packages/bruno-electron/src/utils/workspace-config.js
New apiSpecs array in workspace config; added getWorkspaceApiSpecs, addApiSpecToWorkspace, removeApiSpecFromWorkspace for workspace-scoped spec management.
Electron Filesystem Helpers
packages/bruno-electron/src/utils/filesystem.js
New file-type predicates: isDotEnvFile, isBrunoConfigFile, isBruEnvironmentConfig, isCollectionRootBruFile.
Electron App Initialization
packages/bruno-electron/src/index.js
Added ApiSpecWatcher instantiation and registerApiSpecIpc initialization during app startup.

Sequence Diagram

sequenceDiagram
    participant User
    participant UI as Frontend (React)
    participant Redux
    participant IPC as Electron IPC
    participant FS as Filesystem
    participant Watcher as ApiSpecWatcher

    User->>UI: Click "Open API Spec"
    UI->>IPC: renderer:open-api-spec
    IPC->>FS: File dialog selection
    FS-->>IPC: Selected path
    IPC->>FS: Read & parse file
    FS-->>IPC: Content + metadata
    IPC->>Watcher: Register watcher for path
    Watcher->>FS: Monitor file changes
    IPC->>UI: Send apispec-tree-updated (addFile)
    UI->>Redux: Dispatch apiSpecAddFileEvent
    Redux-->>UI: Update state (apiSpecs)
    UI->>UI: Render ApiSpecPanel

    User->>UI: Edit YAML content
    UI->>Redux: Dispatch onEdit (local state update)
    
    User->>UI: Click Save
    UI->>Redux: Dispatch saveApiSpecToFile
    Redux->>IPC: renderer:save-api-spec
    IPC->>FS: Write file
    FS-->>IPC: Success
    Watcher->>FS: Detect change
    Watcher->>IPC: Emit main:apispec-tree-updated
    IPC->>UI: Send change event
    UI->>Redux: Dispatch apiSpecChangeFileEvent
    Redux-->>UI: Update state
    UI-->>User: Show toast (Saved)
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Areas requiring extra attention:

  • Redux slice logic (packages/bruno-app/src/providers/ReduxStore/slices/apiSpec.js) — Multiple async thunks with IPC coordination and error handling; ensure action dispatches and state mutations are correct.
  • Electron IPC handlers (packages/bruno-electron/src/ipc/apiSpec.js) — Seven new handlers with filesystem I/O, YAML manipulation, and workspace config updates; verify error paths and Promise resolution.
  • Workspace integration (packages/bruno-electron/src/utils/workspace-config.js, packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js) — API spec CRUD operations on workspace.yml and Redux state; ensure consistency across add/remove/update flows.
  • YAML plugin implementation (packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/Plugins/Yaml/index.js) — Custom CodeMirror syntax highlighting; validate token mappings and folding logic.
  • API spec exporter (packages/bruno-app/src/utils/exporters/openapi-spec.js) — Complex OpenAPI 3.0.0 generation with XML/JSON parsing, schema inference, and auth scheme mapping; review parsing error handling and warning accumulation.
  • Control flow changes (packages/bruno-app/src/pages/Bruno/index.js, packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js) — Multi-view tab logic removal and conditional rendering; verify sidebar and main page rendering order and state transitions.

Possibly related PRs

Suggested labels

size/XXL

Suggested reviewers

  • lohit-bruno
  • bijin-bruno
  • helloanoop

Poem

🎨 From specs to render, a feature takes flight,
YAML editors gleam in the neon light,
Watchers and slices in Redux align,
Swagger UI glows on the UI design,
Workspaces hold APIs, pristine and tight!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 'add: apispec support' clearly and concisely describes the main objective of the pull request—adding API specification support to the Bruno application.
✨ 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.

Copy link

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

Note

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

🟠 Major comments (14)
packages/bruno-electron/src/ipc/collection.js-1558-1576 (1)

1558-1576: Synchronous I/O operations block the main Electron process.

Using fs.readdirSync, fs.lstatSync, and fs.readFileSync in an async IPC handler will block the main process, degrading UI responsiveness for large collections. Consider using async alternatives:

-        const filesInCurrentDir = fs.readdirSync(currentPath);
+        const filesInCurrentDir = await fs.promises.readdir(currentPath);
-            const stats = fs.lstatSync(filePath);
+            const stats = await fs.promises.lstat(filePath);
-              const content = fs.readFileSync(filePath, 'utf8');
+              const content = await fs.promises.readFile(filePath, 'utf8');

Apply similar changes to all readFileSync calls within this handler (lines 1580, 1591, 1608, 1627, 1636).

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

packages/bruno-electron/src/ipc/collection.js-1568-1570 (1)

1568-1570: Directory filter logic uses full path instead of directory name.

The conditions !filePath.startsWith('.git') and !filePath.startsWith('node_modules') check the full path, not the directory name. A path like /home/user/.git-projects/collection would be incorrectly filtered.

-          if (stats.isDirectory() && !filePath.startsWith('.git') && !filePath.startsWith('node_modules')) {
+          if (stats.isDirectory() && !file.startsWith('.git') && file !== 'node_modules') {

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

packages/bruno-electron/src/ipc/workspace.js-153-188 (1)

153-188: Consider using yaml.safeLoad and validating API spec structure.

The handler correctly validates workspacePath and resolves relative paths. However:

  1. Security concern: Line 166 uses yaml.load() which can execute arbitrary code. Use yaml.safeLoad() (deprecated) or yaml.load(yamlContent, { schema: yaml.SAFE_SCHEMA }) for safer parsing.

  2. Data validation: The code assumes each apiSpec in the array has a path property (line 175). Consider validating the structure to prevent runtime errors.

Apply this diff to improve security:

-      const workspaceConfig = yaml.load(yamlContent);
+      const workspaceConfig = yaml.load(yamlContent, { schema: yaml.SAFE_SCHEMA });

Additionally, consider adding validation:

       const resolvedApiSpecs = apiSpecs.map((apiSpec) => {
+        if (!apiSpec || typeof apiSpec !== 'object') {
+          return null;
+        }
         if (apiSpec.path && !path.isAbsolute(apiSpec.path)) {
           return {
             ...apiSpec,
             path: path.join(workspacePath, apiSpec.path)
           };
         }
         return apiSpec;
-      });
+      }).filter(Boolean);
packages/bruno-app/src/components/ApiSpecPanel/FileEditor/index.js-14-14 (1)

14-14: Stale state: content won't update when apiSpec changes.

If the user switches to a different API spec, content will retain the previous spec's value since useState only uses the initial value once.

Add an effect to sync state when apiSpec changes:

 const [content, setContent] = useState(apiSpec?.raw);

+useEffect(() => {
+  setContent(apiSpec?.raw);
+}, [apiSpec?.uid]);

Don't forget to import useEffect from React.

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

packages/bruno-electron/src/app/apiSpecs.js-25-27 (1)

25-27: Move require statements to module top-level.

fs, path, and js-yaml are required inline multiple times (lines 25-27, 83-84, 87-88, 90). This adds overhead and reduces readability. Move to top-level imports.

 const { dialog, ipcMain } = require('electron');
 const { isDirectory, normalizeAndResolvePath } = require('../utils/filesystem');
 const { generateUidBasedOnHash } = require('../utils/common');
+const fs = require('fs');
+const path = require('path');
+const yaml = require('js-yaml');

Also applies to: 80-96

packages/bruno-app/src/utils/exporters/openapi-spec.js-332-341 (1)

332-341: Invalid security scheme type digest.

OpenAPI 3.0 doesn't support type: 'digest'. Digest auth should use type: 'http' with scheme: 'digest'.

         case 'digest':
           components.securitySchemes[securitySchemaId] = {
-            type: 'digest',
+            type: 'http',
             scheme: 'digest',
             description: 'Digest Authentication'
           };
packages/bruno-app/src/utils/exporters/openapi-spec.js-299-318 (1)

299-318: Incorrect OAuth2 flow for client_credentials.

The client_credentials grant type incorrectly uses the password flow. OpenAPI 3.0 specifies clientCredentials for this grant type.

         case 'client_credentials':
           components.securitySchemes[securitySchemaId] = {
             type: 'oauth2',
             flows: {
-              password: {
+              clientCredentials: {
                 tokenUrl: accessTokenUrl,
packages/bruno-app/src/utils/exporters/openapi-spec.js-256-256 (1)

256-256: Unsafe destructuring from optional chain.

If auth?.oauth2 is nullish, destructuring throws. Add a fallback.

-          const { authorizationUrl, accessTokenUrl, callbackUrl, scope } = auth?.oauth2;
+          const { authorizationUrl, accessTokenUrl, callbackUrl, scope } = auth?.oauth2 || {};
packages/bruno-app/src/utils/exporters/openapi-spec.js-35-35 (1)

35-35: Platform-specific path separator.

Using \\ as separator only works on Windows. Consider using a platform-agnostic approach or regex that handles both / and \\.

-    const parts = pathname.split('\\');
+    const parts = pathname.split(/[\\/]/);
packages/bruno-app/src/utils/exporters/openapi-spec.js-61-81 (1)

61-81: Unsafe optional chaining with spread operator.

Static analysis correctly flags lines 62, 69, 76: spreading undefined (from short-circuited optional chains) into an array throws TypeError. Add fallback empty arrays.

       const parameters = [
-        ...params?.map((param) => ({
+        ...(params?.map((param) => ({
           name: param?.name,
           in: 'query',
           description: '',
           required: param?.enabled,
           example: param?.value
-        })),
-        ...headers?.map((header) => ({
+        })) || []),
+        ...(headers?.map((header) => ({
           name: header?.name,
           in: 'header',
           description: '',
           required: header?.enabled,
           example: header?.value
-        })),
-        ...pathMatches?.map((path) => ({
+        })) || []),
+        ...(pathMatches?.map((path) => ({
           name: path.slice(1, path.length - 1),
           in: 'path',
           required: true
-        }))
+        })) || [])
       ];
packages/bruno-electron/src/app/apiSpecs.js-12-14 (1)

12-14: Missing await for async function.

openApiSpec is async but not awaited. Errors thrown inside won't be caught by the surrounding try-catch.

   try {
-    openApiSpec(win, watcher, resolvedPath, options);
+    await openApiSpec(win, watcher, resolvedPath, options);
   } catch (err) {
packages/bruno-electron/src/app/apiSpecs.js-6-8 (1)

6-8: Invalid dialog property createFile.

Electron's dialog.showOpenDialog doesn't support createFile. Supported properties are openFile, openDirectory, multiSelections, showHiddenFiles, createDirectory (macOS), and promptToCreate (Windows). This unsupported property will be silently ignored.

packages/bruno-electron/src/app/apiSpecsWatcher.js-32-68 (1)

32-68: Eliminate code duplication and redundant file reads.

The add and change functions are nearly identical, differing only in the IPC event name. Additionally, both functions read the file twice: once in parseApiSpecContent and again to get the raw content.

Refactor to a shared helper:

+const buildFilePayload = (pathname) => {
+  const basename = path.basename(pathname);
+  const raw = fs.readFileSync(pathname, 'utf8');
+  const extension = path.extname(pathname).toLowerCase();
+  
+  let apiSpecContent = null;
+  if (extension === '.yaml' || extension === '.yml') {
+    apiSpecContent = yaml.load(raw);
+  } else if (extension === '.json') {
+    apiSpecContent = safeParseJSON(raw);
+  }
+  
+  const file = {
+    raw,
+    name: apiSpecContent?.info?.title || basename.split('.')[0],
+    filename: basename,
+    pathname,
+    json: apiSpecContent
+  };
+  
+  hydrateApiSpecWithUuid(file, pathname);
+  return file;
+};
+
 const add = async (win, pathname) => {
   if (!hasApiSpecExtension(pathname)) return;
   try {
-    const basename = path.basename(pathname);
-    const file = {};
-    const apiSpecContent = parseApiSpecContent(pathname);
-
-    file.raw = fs.readFileSync(pathname, 'utf8');
-    file.name = apiSpecContent?.info?.title || basename.split('.')[0];
-    file.filename = basename;
-    file.pathname = pathname;
-    file.json = apiSpecContent;
-    hydrateApiSpecWithUuid(file, pathname);
+    const file = buildFilePayload(pathname);
     win.webContents.send('main:apispec-tree-updated', 'addFile', file);
   } catch (err) {
     console.error(err);
   }
 };
 
 const change = async (win, pathname) => {
   if (!hasApiSpecExtension(pathname)) return;
   try {
-    const basename = path.basename(pathname);
-    const file = {};
-    const apiSpecContent = parseApiSpecContent(pathname);
-
-    file.raw = fs.readFileSync(pathname, 'utf8');
-    file.name = apiSpecContent?.info?.title || basename.split('.')[0];
-    file.filename = basename;
-    file.pathname = pathname;
-    file.json = apiSpecContent;
-    hydrateApiSpecWithUuid(file, pathname);
+    const file = buildFilePayload(pathname);
     win.webContents.send('main:apispec-tree-updated', 'changeFile', file);
   } catch (err) {
     console.error(err);
   }
 };

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

packages/bruno-electron/src/app/apiSpecsWatcher.js-113-114 (1)

113-114: Function signature mismatch: unused parameters.

The add and change functions are called with 5 parameters (win, pathname, apiSpecUid, watchPath, workspacePath), but their definitions (lines 32, 51) only accept 2 parameters (win, pathname). The extra parameters are silently dropped.

Verify whether apiSpecUid, watchPath, and workspacePath are needed for proper functionality. If they are required (e.g., for workspace association), update the function signatures:

-const add = async (win, pathname) => {
+const add = async (win, pathname, apiSpecUid, watchPath, workspacePath) => {
   if (!hasApiSpecExtension(pathname)) return;
   try {
     // ... existing logic ...
+    // Use apiSpecUid, watchPath, workspacePath as needed
   }
 };

-const change = async (win, pathname) => {
+const change = async (win, pathname, apiSpecUid, watchPath, workspacePath) => {
   if (!hasApiSpecExtension(pathname)) return;
   try {
     // ... existing logic ...
+    // Use apiSpecUid, watchPath, workspacePath as needed
   }
 };

If the parameters are not needed, remove them from the function calls.

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

🟡 Minor comments (8)
packages/bruno-electron/src/ipc/collection.js-1609-1611 (1)

1609-1611: Unused variable environmentName.

environmentName is computed but never used. Either remove it or use it as the key in envVariables (line 1616) instead of path.basename(filePath) which includes the extension.

              const environmentFilepathBasename = path.basename(filePath);
              const environmentName = environmentFilepathBasename.substring(0, environmentFilepathBasename.length - 4);
              let data = await parseEnvironment(bruContent);
              variables = {
                ...variables,
                envVariables: {
                  ...(variables?.envVariables || {}),
-                  [path.basename(filePath)]: data.variables
+                  [environmentName]: data.variables
                }
              };
packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecItem/index.js-1-64 (1)

1-64: Tweak equality check and imports; behavior otherwise looks good

The item wiring (open on click, set active UID, show Close modal via Dropdown) looks correct and aligns with the Redux state.

Two small improvements:

  1. Use strict equality for active state

    showApiSpecPage && apiSpec?.uid == activeApiSpecUid ? 'active' : ''

    To avoid unexpected type coercion, it’s safer to use strict equality:

    showApiSpecPage && apiSpec?.uid === activeApiSpecUid ? 'active' : ''
  2. Minor import cleanup (optional)
    You currently import from react twice (useState/useRef and forwardRef). You can consolidate to a single import:

    import { useState, useRef, forwardRef } from 'react';

Everything else in this component looks solid.

packages/bruno-app/src/components/ApiSpecPanel/FileEditor/index.js-44-44 (1)

44-44: Typo: oapcity-100 should be opacity-100.

This typo prevents the opacity class from applying.

-        hasChanges ? 'cursor-pointer oapcity-100' : 'cursor-default opacity-50'
+        hasChanges ? 'cursor-pointer opacity-100' : 'cursor-default opacity-50'
packages/bruno-app/src/components/ApiSpecPanel/Renderers/Swagger/index.js-8-8 (1)

8-8: Remove debug console.log before merging.

This debug statement will pollute the console in production.

-  console.log('string', string);
packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/index.js-34-34 (1)

34-34: Typo: "hightlighting" → "highlighting".

-        // YAML linting and hightlighting plugin
+        // YAML linting and highlighting plugin
packages/bruno-app/src/components/Sidebar/ApiSpecs/CreateApiSpec/index.js-110-110 (1)

110-110: Typo: "diolog" → "dialog".

-        // When the user closes the diolog without selecting anything dirPath will be false
+        // When the user closes the dialog without selecting anything dirPath will be false
packages/bruno-electron/src/ipc/apiSpec.js-21-28 (1)

21-28: Missing return on success path.

Line 24 creates a Promise.resolve() that's not returned. On success, the handler returns undefined instead of explicitly resolving. While this may work, it's inconsistent with the error path.

 ipcMain.handle('renderer:save-api-spec', async (event, pathname, content) => {
   try {
     await writeFile(pathname, content);
-    Promise.resolve();
+    return;
   } catch (error) {
     return Promise.reject(error);
   }
 });
packages/bruno-electron/src/app/apiSpecsWatcher.js-1-1 (1)

1-1: Remove unused lodash import.

The lodash library is imported but never used in this file.

Apply this diff:

-const _ = require('lodash');
 const fs = require('fs');
🧹 Nitpick comments (34)
packages/bruno-electron/src/ipc/collection.js (3)

1560-1562: Duplicate and unreliable node_modules check.

The currentPath.includes('node_modules') check is duplicated (also at line 1568) and would incorrectly match paths like /home/user/node_modules_backup/. Since you're already filtering at the directory traversal level (line 1568), this check can be removed or tightened.

-        if (currentPath.includes('node_modules')) {
-          return;
-        }

1625-1634: Incomplete implementation flagged with TODO.

The isCollectionRootBruFile block parses the collection but doesn't use the result. If this is intentional placeholder code, consider documenting the intended behavior or deferring this code until fully implemented.

Would you like me to help flesh out the logic for processing collection.bru data, or should this be tracked in a separate issue?


1550-1551: Missing input validation for collectionPath.

No validation that collectionPath exists or is a directory before traversal begins. If an invalid path is passed, the error message from readdirSync won't be user-friendly.

  ipcMain.handle('renderer:get-collection-json', async (event, collectionPath) => {
+   if (!collectionPath || !fs.existsSync(collectionPath)) {
+     throw new Error(`Invalid collection path: ${collectionPath}`);
+   }
    let variables = {};
    let name = '';
packages/bruno-app/src/components/ApiSpecPanel/StyledWrapper.js (1)

1-20: Panel styling is fine; consider deduping danger menu item styles

The styles for .menu-icon and the danger dropdown-item.menu-item are correct and consistent with the sidebar. Longer term, you might centralize the “danger” dropdown-item styling (here and in the sidebar ApiSpecs wrapper) into a shared styled wrapper or mixin to avoid duplication, but it’s not blocking.

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

13-14: API spec page flags are wired correctly; double‑check interaction with Preferences

showApiSpecPage is added to initial state and reducers in a consistent way, and showHomePage now correctly clears the API Spec view. One thing to verify: showPreferences currently only toggles state.showPreferences and does not clear state.showApiSpecPage, so it’s possible for both flags to be true. If the intent is strict mutual exclusivity between the API Spec page and Preferences, consider also resetting showApiSpecPage in the preferences-related reducer(s).

Also applies to: 74-88, 126-147

packages/bruno-app/src/components/Sidebar/ApiSpecs/CloseApiSpec/index.js (1)

1-37: Close flow and async handling look solid

The modal correctly dispatches closeApiSpecFile, handles the returned Promise, shows success/error toasts, and only closes on success. Implementation is straightforward and matches existing Modal patterns. As a tiny polish, you may want to standardize the capitalization of “API Spec” in the title/body for consistency, but functionally this is good to go.

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

63-70: Workspace apiSpec wiring is correct; consider normalizing paths like collections

The new apiSpecs handling (init in createWorkspaceConfig, plus getWorkspaceApiSpecs/addApiSpecToWorkspace/removeApiSpecFromWorkspace) is consistent with the existing collections logic and should behave correctly with relative and absolute paths.

For consistency with getWorkspaceCollections, you may want to switch this:

if (apiSpec.path && !path.isAbsolute(apiSpec.path)) {
  return {
    ...apiSpec,
    path: path.join(workspacePath, apiSpec.path)
  };
}

to use path.resolve(workspacePath, apiSpec.path) instead of path.join(...). That will normalize any .. segments and mirror the collection path handling.

Also applies to: 215-291, 293-309

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

10-57: ApiSpecs integration works; consider spec‑only workspaces and divider simplification

The new ApiSpecs block is correctly wired into the Collections sidebar and will render underneath the collections list.

Two small points to consider:

  1. Visibility when there are no collections
    Because of the early return when workspaceCollections is empty, the ApiSpecs section won’t render at all in that case. If you expect workspaces that might only contain API specs (no collections), you may want to render ApiSpecs outside that early return so they’re still accessible.

  2. Divider markup (optional)
    The spacer

    <div className="w-full my-2" style={{ height: 1 }}></div>

    is effectively used just for vertical spacing. You could likely drop the inline style={{ height: 1 }} and rely on my-2, or replace it with a themed horizontal rule if you want a visible separator line.

packages/bruno-app/src/components/ApiSpecPanel/Renderers/Swagger/index.js (1)

5-5: Consider a more descriptive prop name.

string is ambiguous. A name like spec or content would better convey the expected value.

-const Swagger = ({ string }) => {
+const Swagger = ({ spec }) => {

Then update line 13:

-        <SwaggerUI spec={string} />
+        <SwaggerUI spec={spec} />
packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/StyledWrapper.js (3)

14-19: Use theme props instead of hardcoded colors.

Hardcoded hex values break theme consistency. Based on learnings, use props.theme for colors.

   input {
     background: transparent;
-    border: 1px solid #d3d6db;
+    border: 1px solid ${(props) => props.theme.input.border};
     outline: none;
     border-radius: 0px;
   }

31-55: Address the TODO: Replace hardcoded Monokai overrides with theme-aware styling.

The !important overrides and hardcoded colors should be refactored to use theme props for maintainability and proper dark/light mode support.

Would you like me to help design a theme-aware approach for these syntax highlighting colors?


57-62: Use semantic theme colors for validation indicators.

Hardcoded green and red won't adapt to themes. Consider using theme tokens like props.theme.colors.success and props.theme.colors.danger.

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

24-24: Use strict equality.

Prefer !== over != for comparison.

-  const hasChanges = Boolean(content != apiSpec?.raw);
+  const hasChanges = Boolean(content !== apiSpec?.raw);
packages/bruno-app/src/components/ApiSpecPanel/index.js (2)

31-37: Move MenuIcon outside the component.

Defining MenuIcon inside ApiSpecPanel recreates it on every render, which can cause unnecessary re-renders for Tippy/Dropdown.

+const MenuIcon = forwardRef((props, ref) => {
+  return (
+    <div ref={ref}>
+      <IconDots size={22} />
+    </div>
+  );
+});

 const ApiSpecPanel = () => {
   // ...
-  const MenuIcon = forwardRef((props, ref) => {
-    return (
-      <div ref={ref}>
-        <IconDots size={22} />
-      </div>
-    );
-  });

1-1: Consolidate React imports.

Multiple React imports can be combined for cleaner code.

-import React, { forwardRef, useRef } from 'react';
+import React, { forwardRef, useRef, useState, Suspense } from 'react';
 // ...
-import { useState } from 'react';
 // ...
-import { Suspense } from 'react';

Also applies to: 9-9, 11-11

packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/Plugins/Yaml/index.js (2)

3-4: Prefer const over var.

Modern JavaScript convention. var can cause scoping issues.

-    var cons = ['true', 'false', 'on', 'off', 'yes', 'no'];
-    var keywordRegex = new RegExp('\\b((' + cons.join(')|(') + '))$', 'i');
+    const cons = ['true', 'false', 'on', 'off', 'yes', 'no'];
+    const keywordRegex = new RegExp('\\b((' + cons.join(')|(') + '))$', 'i');

7-8: Consider using const for ch and esc as well.

These variables are not reassigned within their scope.

-        var ch = stream.peek();
-        var esc = state.escaped;
+        const ch = stream.peek();
+        const esc = state.escaped;
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js (1)

204-207: Use object shorthand.

Per ES6 conventions, apiSpecs: apiSpecs can be simplified.

       dispatch(updateWorkspace({
         uid: workspaceUid,
-        apiSpecs: apiSpecs
+        apiSpecs
       }));
packages/bruno-app/src/components/Sidebar/ApiSpecs/index.js (2)

23-33: Redundant dependency in useMemo.

activeWorkspace?.apiSpecs is derived from activeWorkspace, so listing it separately is unnecessary.

-  }, [allApiSpecs, activeWorkspace, activeWorkspace?.apiSpecs]);
+  }, [allApiSpecs, activeWorkspace]);

66-70: Redundant null check.

The condition at line 47 already guarantees apiSpecs is non-empty when this branch renders.

         <div className="flex flex-col top-32 bottom-10 left-0 right-0 py-4">
-          {apiSpecs && apiSpecs.length
-            ? apiSpecs.map((apiSpec) => {
-                return <ApiSpecItem apiSpec={apiSpec} key={apiSpec.uid} />;
-              })
-            : null}
+          {apiSpecs.map((apiSpec) => (
+            <ApiSpecItem apiSpec={apiSpec} key={apiSpec.uid} />
+          ))}
         </div>
packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/index.js (2)

74-78: Potential issue: editor referenced before assignment.

The Tab handler uses editor.getLine() but editor is assigned from the outer scope in the same expression. This works due to closure semantics, but it's fragile. Consider using cm consistently.

         'Tab': function (cm) {
-          cm.getSelection().includes('\n') || editor.getLine(cm.getCursor().line) == cm.getSelection()
+          cm.getSelection().includes('\n') || cm.getLine(cm.getCursor().line) === cm.getSelection()
             ? cm.execCommand('indentMore')
             : cm.replaceSelection('  ', 'end');
         },

113-116: Side effect in render method.

Calling editor.refresh() in render() is unconventional and can cause performance issues. Consider moving this to componentDidUpdate when relevant props change.

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

8-8: Unused parameter lastOpenedApiSpecs.

The parameter is passed to both registerRendererEventHandlers and registerMainEventHandlers but never referenced. Either remove it or document its intended future use.

Also applies to: 106-106, 117-119


49-74: Inline require and synchronous file I/O.

The js-yaml require at line 50 should be at module top-level. Additionally, readFileSync/writeFileSync on lines 54 and 74 block the main process. Consider using async versions for better responsiveness.

+const yaml = require('js-yaml');
 // ... at top of file

 ipcMain.handle('renderer:remove-api-spec', async (event, pathname, workspacePath = null) => {
   try {
     if (watcher && mainWindow) {
       watcher.removeWatcher(pathname, mainWindow);
       removeApiSpecUid(pathname);

       if (workspacePath) {
-        const yaml = require('js-yaml');
         const workspaceFilePath = path.join(workspacePath, 'workspace.yml');

         if (fs.existsSync(workspaceFilePath)) {
-          const yamlContent = fs.readFileSync(workspaceFilePath, 'utf8');
+          const yamlContent = await fs.promises.readFile(workspaceFilePath, 'utf8');

84-91: Consider adding timeout and error handling for fetch.

The fetch call has no timeout, which could hang indefinitely on unresponsive URLs. Also, non-2xx responses won't throw and will return unexpected content.

 ipcMain.handle('renderer:fetch-api-spec', async (event, url) => {
   try {
-    const data = await fetch(url).then((res) => res.text());
+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => controller.abort(), 30000);
+    const res = await fetch(url, { signal: controller.signal });
+    clearTimeout(timeoutId);
+    if (!res.ok) {
+      throw new Error(`HTTP ${res.status}: ${res.statusText}`);
+    }
+    const data = await res.text();
     return data;
   } catch (error) {
     return Promise.reject(error);
   }
 });
packages/bruno-app/src/providers/ReduxStore/slices/apiSpec.js (3)

64-64: Use strict equality.

Line 64 uses == which allows type coercion. Prefer === for consistency with line 65.

-    let apiSpecIndex = state.apiSpecs.findIndex((c) => c.uid == uid);
+    let apiSpecIndex = state.apiSpecs.findIndex((c) => c.uid === uid);

137-160: Redundant null check and unreachable code.

Line 140's if (apiSpec) is always true after the early return on line 138. Line 160's return; is unreachable.

       if (!apiSpec) {
         return reject(new Error('API Spec not found'));
       }
-      if (apiSpec) {
-        const { ipcRenderer } = window;
+      const { ipcRenderer } = window;
         // ... rest of logic
-      }
-      return;
     });

151-154: Dynamic require inside async callback.

Using require() inside the .then() callback works but is unconventional in ES modules context. Consider importing at the top level if circular dependencies allow.

packages/bruno-app/src/components/Sidebar/ApiSpecs/CreateApiSpec/index.js (2)

1-1: Consolidate React imports.

useState is imported separately on line 9 while useRef, useEffect are imported with React on line 1. Consolidate for consistency.

-import React, { useRef, useEffect } from 'react';
+import React, { useRef, useEffect, useState } from 'react';
 // ...
-import { useState } from 'react';

Also applies to: 9-9


60-73: Validation schema doesn't conditionally require collectionLocation.

When importFrom === 'collection', collectionLocation should be required but the schema only specifies min(1). Consider using Yup's when() for conditional validation.

-collectionLocation: Yup.string().min(1, 'location is required'),
+collectionLocation: Yup.string().when('importFrom', {
+  is: 'collection',
+  then: (schema) => schema.min(1, 'location is required').required('Collection location is required'),
+  otherwise: (schema) => schema
+}),
packages/bruno-electron/src/app/apiSpecs.js (1)

86-95: Redundant file reads and potential race condition.

Lines 83 and 88 both call readFileSync(apiSpecPath, 'utf8'). The file is read twice, and if the file changes between reads, raw and json could be inconsistent. Read once and reuse.

+    const content = fs.readFileSync(apiSpecPath, 'utf8');
+    const ext = path.extname(apiSpecPath).toLowerCase();
     win.webContents.send('main:apispec-tree-updated', 'addFile', {
       pathname: apiSpecPath,
       uid: uid,
-      raw: require('fs').readFileSync(apiSpecPath, 'utf8'),
-      name: require('path').basename(apiSpecPath, require('path').extname(apiSpecPath)),
-      filename: require('path').basename(apiSpecPath),
-      json: (() => {
-        const ext = require('path').extname(apiSpecPath).toLowerCase();
-        const content = require('fs').readFileSync(apiSpecPath, 'utf8');
-        if (ext === '.yaml' || ext === '.yml') {
-          return require('js-yaml').load(content);
-        } else if (ext === '.json') {
-          return JSON.parse(content);
-        }
-        return null;
-      })()
+      raw: content,
+      name: path.basename(apiSpecPath, path.extname(apiSpecPath)),
+      filename: path.basename(apiSpecPath),
+      json: ext === '.yaml' || ext === '.yml'
+        ? yaml.load(content)
+        : ext === '.json'
+          ? JSON.parse(content)
+          : null
     });
packages/bruno-electron/src/app/apiSpecsWatcher.js (3)

15-25: Add error handling for file I/O and parsing.

Both fs.readFileSync (line 17) and yaml.load (line 20) can throw exceptions. While callers currently wrap this in try-catch, error handling within the helper would make it more robust and reusable.

Consider this approach:

 const parseApiSpecContent = (pathname) => {
+  try {
     const extension = path.extname(pathname).toLowerCase();
     let content = fs.readFileSync(pathname, 'utf8');
 
     if (extension === '.yaml' || extension === '.yml') {
       return yaml.load(content);
     } else if (extension === '.json') {
       return safeParseJSON(content);
     }
     return null;
+  } catch (err) {
+    console.error('Error parsing API spec:', pathname, err);
+    return null;
+  }
 };

124-132: Use delete for consistent memory cleanup.

Line 127 sets this.watchers[watchPath] = null, which leaves the key in the object, while line 130 uses delete for watcherWorkspaces. For proper memory management and consistency, use delete for both.

 removeWatcher(watchPath, win) {
   if (this.watchers[watchPath]) {
     this.watchers[watchPath].close();
-    this.watchers[watchPath] = null;
+    delete this.watchers[watchPath];
   }
   if (this.watcherWorkspaces[watchPath]) {
     delete this.watcherWorkspaces[watchPath];
   }
 }

120-122: Consider explicit boolean return for clarity.

The method name hasWatcher implies a boolean return, but it currently returns the watcher object or undefined. While this works for boolean checks, an explicit boolean return would be clearer.

 hasWatcher(watchPath) {
-  return this.watchers[watchPath];
+  return !!this.watchers[watchPath];
 }

Alternatively, if external code needs the watcher instance, consider renaming to getWatcher.

📜 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 3302284 and cd0f1e4.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (37)
  • packages/bruno-app/package.json (2 hunks)
  • packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/Plugins/Yaml/index.js (1 hunks)
  • packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/index.js (1 hunks)
  • packages/bruno-app/src/components/ApiSpecPanel/FileEditor/index.js (1 hunks)
  • packages/bruno-app/src/components/ApiSpecPanel/Renderers/Swagger/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ApiSpecPanel/Renderers/Swagger/index.js (1 hunks)
  • packages/bruno-app/src/components/ApiSpecPanel/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/ApiSpecPanel/index.js (1 hunks)
  • packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecBadge/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecBadge/index.js (1 hunks)
  • packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecItem/index.js (1 hunks)
  • packages/bruno-app/src/components/Sidebar/ApiSpecs/CloseApiSpec/index.js (1 hunks)
  • packages/bruno-app/src/components/Sidebar/ApiSpecs/CreateApiSpec/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/Sidebar/ApiSpecs/CreateApiSpec/index.js (1 hunks)
  • packages/bruno-app/src/components/Sidebar/ApiSpecs/StyledWrapper.js (1 hunks)
  • packages/bruno-app/src/components/Sidebar/ApiSpecs/index.js (1 hunks)
  • packages/bruno-app/src/components/Sidebar/Collections/index.js (2 hunks)
  • packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js (0 hunks)
  • packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (7 hunks)
  • packages/bruno-app/src/components/Sidebar/index.js (1 hunks)
  • packages/bruno-app/src/pages/Bruno/index.js (3 hunks)
  • packages/bruno-app/src/providers/App/useIpcEvents.js (3 hunks)
  • packages/bruno-app/src/providers/ReduxStore/index.js (2 hunks)
  • packages/bruno-app/src/providers/ReduxStore/slices/apiSpec.js (1 hunks)
  • packages/bruno-app/src/providers/ReduxStore/slices/app.js (3 hunks)
  • packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js (3 hunks)
  • packages/bruno-app/src/utils/exporters/openapi-spec.js (1 hunks)
  • packages/bruno-electron/src/app/apiSpecs.js (1 hunks)
  • packages/bruno-electron/src/app/apiSpecsWatcher.js (1 hunks)
  • packages/bruno-electron/src/cache/apiSpecUids.js (1 hunks)
  • packages/bruno-electron/src/index.js (3 hunks)
  • packages/bruno-electron/src/ipc/apiSpec.js (1 hunks)
  • packages/bruno-electron/src/ipc/collection.js (3 hunks)
  • packages/bruno-electron/src/ipc/workspace.js (1 hunks)
  • packages/bruno-electron/src/utils/filesystem.js (2 hunks)
  • packages/bruno-electron/src/utils/workspace-config.js (3 hunks)
💤 Files with no reviewable changes (1)
  • packages/bruno-app/src/components/Sidebar/SidebarHeader/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/Sidebar/ApiSpecs/ApiSpecBadge/StyledWrapper.js
  • packages/bruno-app/src/providers/ReduxStore/index.js
  • packages/bruno-app/src/components/Sidebar/ApiSpecs/CloseApiSpec/index.js
  • packages/bruno-app/src/components/ApiSpecPanel/StyledWrapper.js
  • packages/bruno-app/src/components/Sidebar/ApiSpecs/StyledWrapper.js
  • packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecItem/index.js
  • packages/bruno-app/src/pages/Bruno/index.js
  • packages/bruno-app/src/components/ApiSpecPanel/Renderers/Swagger/index.js
  • packages/bruno-app/src/components/Sidebar/ApiSpecs/index.js
  • packages/bruno-electron/src/ipc/workspace.js
  • packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/StyledWrapper.js
  • packages/bruno-app/src/components/Sidebar/Collections/index.js
  • packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecBadge/index.js
  • packages/bruno-app/src/components/ApiSpecPanel/FileEditor/index.js
  • packages/bruno-app/src/components/Sidebar/index.js
  • packages/bruno-app/src/components/ApiSpecPanel/Renderers/Swagger/StyledWrapper.js
  • packages/bruno-electron/src/app/apiSpecs.js
  • packages/bruno-app/src/utils/exporters/openapi-spec.js
  • packages/bruno-electron/src/utils/workspace-config.js
  • packages/bruno-electron/src/cache/apiSpecUids.js
  • packages/bruno-app/src/components/Sidebar/ApiSpecs/CreateApiSpec/StyledWrapper.js
  • packages/bruno-electron/src/utils/filesystem.js
  • packages/bruno-app/src/providers/ReduxStore/slices/apiSpec.js
  • packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/index.js
  • packages/bruno-app/src/components/ApiSpecPanel/index.js
  • packages/bruno-electron/src/ipc/apiSpec.js
  • packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js
  • packages/bruno-app/src/providers/ReduxStore/slices/app.js
  • packages/bruno-electron/src/index.js
  • packages/bruno-app/src/providers/App/useIpcEvents.js
  • packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/Plugins/Yaml/index.js
  • packages/bruno-app/src/components/Sidebar/ApiSpecs/CreateApiSpec/index.js
  • packages/bruno-electron/src/app/apiSpecsWatcher.js
  • packages/bruno-electron/src/ipc/collection.js
  • packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js
🧠 Learnings (3)
📚 Learning: 2025-12-05T20:31:32.975Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:32.975Z
Learning: Applies to **/*.{jsx,tsx} : Styled Components are used as wrappers to define both self and children components style; Tailwind classes are used specifically for layout based styles

Applied to files:

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

Applied to files:

  • packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecBadge/StyledWrapper.js
  • packages/bruno-app/src/components/ApiSpecPanel/StyledWrapper.js
  • packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/StyledWrapper.js
  • packages/bruno-app/src/components/ApiSpecPanel/Renderers/Swagger/StyledWrapper.js
  • packages/bruno-app/src/components/Sidebar/ApiSpecs/CreateApiSpec/StyledWrapper.js
📚 Learning: 2025-12-05T20:31:32.975Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:32.975Z
Learning: Applies to **/*.{jsx,tsx} : Styled Component CSS might also change layout but Tailwind classes shouldn't define colors

Applied to files:

  • packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecBadge/StyledWrapper.js
  • packages/bruno-app/src/components/ApiSpecPanel/StyledWrapper.js
  • packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/StyledWrapper.js
  • packages/bruno-app/src/components/ApiSpecPanel/Renderers/Swagger/StyledWrapper.js
  • packages/bruno-app/src/components/Sidebar/ApiSpecs/CreateApiSpec/StyledWrapper.js
🧬 Code graph analysis (17)
packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecBadge/StyledWrapper.js (4)
packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/StyledWrapper.js (1)
  • StyledWrapper (3-63)
packages/bruno-app/src/components/ApiSpecPanel/StyledWrapper.js (1)
  • StyledWrapper (3-20)
packages/bruno-app/src/components/Sidebar/ApiSpecs/CreateApiSpec/StyledWrapper.js (1)
  • StyledWrapper (3-13)
packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js (1)
  • StyledWrapper (3-104)
packages/bruno-app/src/components/Sidebar/ApiSpecs/CloseApiSpec/index.js (2)
packages/bruno-app/src/providers/ReduxStore/slices/apiSpec.js (5)
  • closeApiSpecFile (131-162)
  • closeApiSpecFile (131-162)
  • apiSpec (19-19)
  • apiSpec (43-43)
  • apiSpec (54-54)
packages/bruno-app/src/components/Modal/index.js (1)
  • Modal (60-161)
packages/bruno-app/src/components/ApiSpecPanel/StyledWrapper.js (5)
packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/StyledWrapper.js (1)
  • StyledWrapper (3-63)
packages/bruno-app/src/components/ApiSpecPanel/Renderers/Swagger/StyledWrapper.js (1)
  • StyledWrapper (3-17)
packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecBadge/StyledWrapper.js (1)
  • StyledWrapper (3-9)
packages/bruno-app/src/components/Sidebar/ApiSpecs/CreateApiSpec/StyledWrapper.js (1)
  • StyledWrapper (3-13)
packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js (1)
  • StyledWrapper (3-104)
packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecItem/index.js (5)
packages/bruno-app/src/components/Sidebar/ApiSpecs/CloseApiSpec/index.js (2)
  • dispatch (9-9)
  • CloseApiSpec (8-35)
packages/bruno-app/src/components/Sidebar/ApiSpecs/index.js (2)
  • dispatch (16-16)
  • handleOpenApiSpec (35-39)
packages/bruno-app/src/pages/Bruno/index.js (2)
  • activeApiSpecUid (57-57)
  • showApiSpecPage (60-60)
packages/bruno-app/src/providers/ReduxStore/slices/apiSpec.js (3)
  • apiSpec (19-19)
  • apiSpec (43-43)
  • apiSpec (54-54)
packages/bruno-app/src/components/Dropdown/index.js (1)
  • Dropdown (5-25)
packages/bruno-app/src/pages/Bruno/index.js (1)
packages/bruno-app/src/components/ApiSpecPanel/index.js (2)
  • useSelector (20-20)
  • ApiSpecPanel (15-95)
packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecBadge/index.js (1)
packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecBadge/StyledWrapper.js (1)
  • StyledWrapper (3-9)
packages/bruno-app/src/components/ApiSpecPanel/FileEditor/index.js (2)
packages/bruno-app/src/providers/ReduxStore/slices/apiSpec.js (5)
  • apiSpec (19-19)
  • apiSpec (43-43)
  • apiSpec (54-54)
  • saveApiSpecToFile (95-115)
  • saveApiSpecToFile (95-115)
packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/index.js (1)
  • CodeEditor (19-138)
packages/bruno-app/src/components/Sidebar/index.js (1)
packages/bruno-app/src/components/Sidebar/Collections/index.js (1)
  • Collections (12-60)
packages/bruno-electron/src/app/apiSpecs.js (6)
packages/bruno-electron/src/app/apiSpecsWatcher.js (7)
  • require (5-5)
  • require (7-7)
  • require (8-8)
  • fs (2-2)
  • path (3-3)
  • yaml (6-6)
  • content (17-17)
packages/bruno-electron/src/cache/apiSpecUids.js (3)
  • require (14-14)
  • uid (17-17)
  • uid (28-28)
packages/bruno-electron/src/utils/filesystem.js (5)
  • normalizeAndResolvePath (46-61)
  • fs (2-2)
  • path (1-1)
  • ext (100-100)
  • ext (317-317)
packages/bruno-app/src/providers/ReduxStore/slices/apiSpec.js (2)
  • openApiSpec (81-93)
  • openApiSpec (81-93)
packages/bruno-electron/src/ipc/workspace.js (3)
  • fs (1-1)
  • path (2-2)
  • yaml (7-7)
packages/bruno-electron/src/utils/workspace-config.js (8)
  • fs (1-1)
  • path (2-2)
  • yaml (3-3)
  • workspaceFilePath (48-48)
  • workspaceFilePath (73-73)
  • yamlContent (79-79)
  • yamlContent (90-94)
  • workspaceConfig (80-80)
packages/bruno-electron/src/utils/workspace-config.js (3)
packages/bruno-app/src/components/Sidebar/ApiSpecs/index.js (1)
  • apiSpecs (23-33)
packages/bruno-electron/src/app/apiSpecs.js (1)
  • path (26-26)
packages/bruno-electron/src/utils/filesystem.js (1)
  • path (1-1)
packages/bruno-electron/src/cache/apiSpecUids.js (1)
packages/bruno-electron/src/app/apiSpecsWatcher.js (3)
  • require (5-5)
  • require (7-7)
  • require (8-8)
packages/bruno-app/src/components/ApiSpecPanel/index.js (4)
packages/bruno-app/src/providers/ReduxStore/slices/apiSpec.js (5)
  • apiSpec (19-19)
  • apiSpec (43-43)
  • apiSpec (54-54)
  • openApiSpec (81-93)
  • openApiSpec (81-93)
packages/bruno-app/src/components/ApiSpecPanel/Renderers/Swagger/StyledWrapper.js (1)
  • StyledWrapper (3-17)
packages/bruno-app/src/components/Dropdown/index.js (1)
  • Dropdown (5-25)
packages/bruno-app/src/components/ApiSpecPanel/Renderers/Swagger/index.js (1)
  • Swagger (5-17)
packages/bruno-electron/src/ipc/apiSpec.js (5)
packages/bruno-electron/src/app/apiSpecs.js (7)
  • path (26-26)
  • fs (25-25)
  • openApiSpecDialog (5-18)
  • openApiSpec (20-105)
  • yaml (27-27)
  • workspaceFilePath (29-29)
  • uid (22-22)
packages/bruno-electron/src/cache/apiSpecUids.js (3)
  • removeApiSpecUid (36-38)
  • uid (17-17)
  • uid (28-28)
packages/bruno-electron/src/index.js (2)
  • path (2-2)
  • fs (1-1)
packages/bruno-electron/src/ipc/workspace.js (3)
  • path (2-2)
  • fs (1-1)
  • yaml (7-7)
packages/bruno-electron/src/utils/workspace-config.js (5)
  • path (2-2)
  • fs (1-1)
  • yaml (3-3)
  • workspaceFilePath (48-48)
  • workspaceFilePath (73-73)
packages/bruno-app/src/providers/ReduxStore/slices/app.js (1)
packages/bruno-app/src/providers/ReduxStore/middlewares/tasks/middleware.js (2)
  • state (23-23)
  • state (64-64)
packages/bruno-electron/src/index.js (1)
packages/bruno-electron/src/ipc/apiSpec.js (1)
  • registerApiSpecIpc (117-120)
packages/bruno-app/src/components/Sidebar/ApiSpecs/CreateApiSpec/index.js (3)
packages/bruno-electron/src/utils/filesystem.js (3)
  • validateName (229-244)
  • files (42-42)
  • files (155-155)
packages/bruno-app/src/utils/exporters/openapi-spec.js (2)
  • exportApiSpec (6-376)
  • exportApiSpec (6-376)
packages/bruno-app/src/providers/ReduxStore/slices/apiSpec.js (2)
  • createApiSpecFile (117-129)
  • createApiSpecFile (117-129)
packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js (4)
packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecItem/index.js (2)
  • handleOpenApiSpec (21-24)
  • dispatch (11-11)
packages/bruno-app/src/components/Sidebar/ApiSpecs/index.js (2)
  • handleOpenApiSpec (35-39)
  • dispatch (16-16)
packages/bruno-app/src/components/Sidebar/ApiSpecs/CreateApiSpec/index.js (2)
  • dispatch (28-28)
  • CreateApiSpec (26-324)
packages/bruno-app/src/providers/ReduxStore/slices/apiSpec.js (2)
  • openApiSpec (81-93)
  • openApiSpec (81-93)
🪛 Biome (2.1.2)
packages/bruno-app/src/utils/exporters/openapi-spec.js

[error] 62-62: Unsafe usage of optional chaining.

If it short-circuits with 'undefined' the evaluation will throw TypeError here:

(lint/correctness/noUnsafeOptionalChaining)


[error] 69-69: Unsafe usage of optional chaining.

If it short-circuits with 'undefined' the evaluation will throw TypeError here:

(lint/correctness/noUnsafeOptionalChaining)


[error] 76-76: Unsafe usage of optional chaining.

If it short-circuits with 'undefined' the evaluation will throw TypeError here:

(lint/correctness/noUnsafeOptionalChaining)


[error] 174-194: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 176-179: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 195-215: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 197-200: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 216-226: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 256-256: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 256-256: Unsafe usage of optional chaining.

If it short-circuits with 'undefined' the evaluation will throw TypeError here:

(lint/correctness/noUnsafeOptionalChaining)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: Unit Tests
  • GitHub Check: CLI Tests

Comment on lines +31 to +40
componentWillMount() {
switch (this.props.mode) {
case 'yaml':
// YAML linting and hightlighting plugin
yamlPlugin(CodeMirror);
break;
default:
break;
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Deprecated lifecycle: componentWillMount removed in React 19.

This lifecycle method was deprecated in React 16.3 and removed in React 19. Move this logic to the constructor or componentDidMount.

   constructor(props) {
     super(props);
     this.cachedValue = props.value || '';
     this.variables = {};
     this.lintOptions = {
       esversion: 11,
       expr: true,
       asi: true
     };
+    // Initialize mode-specific plugins
+    if (props.mode === 'yaml') {
+      yamlPlugin(CodeMirror);
+    }
   }
-
-  componentWillMount() {
-    switch (this.props.mode) {
-      case 'yaml':
-        // YAML linting and hightlighting plugin
-        yamlPlugin(CodeMirror);
-        break;
-      default:
-        break;
-    }
-  }

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

🤖 Prompt for AI Agents
In packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/index.js
around lines 31-40, componentWillMount is deprecated/removed; move the
yamlPlugin(CodeMirror) registration into the constructor or componentDidMount.
Replace the lifecycle with a one-time registration in the constructor (or
componentDidMount if it requires the DOM) and add a guard (static/closure
boolean) so the plugin is only registered once across instances to avoid
duplicate registrations.

Comment on lines +39 to +43
const handleOpenApiSpec = () => {
dispatch(openApiSpec()).catch(
(err) => console.log(err) && toast.error('An error occurred while opening the API spec')
);
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Bug: toast.error will never execute.

console.log() returns undefined, which is falsy, so the && short-circuits and toast.error() is never called.

 const handleOpenApiSpec = () => {
-  dispatch(openApiSpec()).catch(
-    (err) => console.log(err) && toast.error('An error occurred while opening the API spec')
-  );
+  dispatch(openApiSpec()).catch((err) => {
+    console.error(err);
+    toast.error('An error occurred while opening the API spec');
+  });
 };
📝 Committable suggestion

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

Suggested change
const handleOpenApiSpec = () => {
dispatch(openApiSpec()).catch(
(err) => console.log(err) && toast.error('An error occurred while opening the API spec')
);
};
const handleOpenApiSpec = () => {
dispatch(openApiSpec()).catch((err) => {
console.error(err);
toast.error('An error occurred while opening the API spec');
});
};
🤖 Prompt for AI Agents
In packages/bruno-app/src/components/ApiSpecPanel/index.js around lines 39 to
43, the catch handler uses `console.log(err) && toast.error(...)` so
`toast.error` never runs because `console.log` returns undefined; change the
catch to call both functions explicitly (for example, use a block: `.catch(err
=> { console.log(err); toast.error('An error occurred while opening the API
spec'); });`) so the error is logged and the toast is shown.

Comment on lines +35 to +39
const handleOpenApiSpec = () => {
dispatch(openApiSpec()).catch(
(err) => console.log(err) && toast.error('An error occurred while opening the API spec')
);
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Bug: Toast error will never show.

console.log(err) returns undefined, so the && short-circuits and toast.error() never executes.

   const handleOpenApiSpec = () => {
-    dispatch(openApiSpec()).catch(
-      (err) => console.log(err) && toast.error('An error occurred while opening the API spec')
-    );
+    dispatch(openApiSpec()).catch((err) => {
+      console.error(err);
+      toast.error('An error occurred while opening the API spec');
+    });
   };
🤖 Prompt for AI Agents
In packages/bruno-app/src/components/Sidebar/ApiSpecs/index.js around lines 35
to 39, the .catch handler uses console.log(err) && toast.error(...), but
console.log returns undefined so the toast never runs; change the catch to a
proper block or sequence so both actions run (for example: .catch(err => {
console.log(err); toast.error('An error occurred while opening the API spec');
}) or use console.error(err); toast.error(...)) ensuring the error is logged and
the toast is shown.

Comment on lines +110 to +113
.catch((reject) => {
toast.error('Error saving file');
resolve();
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical bug: Error swallowed and incorrectly resolved.

The catch block shadows reject with the error parameter and then calls resolve() instead of rejecting. Errors will appear as successes to callers.

-        .catch((reject) => {
+        .catch((error) => {
           toast.error('Error saving file');
-          resolve();
+          reject(error);
         });
🤖 Prompt for AI Agents
In packages/bruno-app/src/providers/ReduxStore/slices/apiSpec.js around lines
110 to 113, the catch handler incorrectly names the error parameter `reject` and
then calls resolve(), which swallows errors and signals success; change the
catch parameter to a descriptive name (e.g., err or error), call reject(err) (or
return Promise.reject(err)) so the caller receives the error, and optionally
include the error message in the toast (e.g., toast.error(`Error saving file:
${err.message}`)) to preserve original error semantics and logging.

Comment on lines +174 to +228
case 'multipartForm':
if (!body?.multipartForm) return;
let multipartFormToKeyValue = body?.multipartForm.reduce((acc, f) => {
acc[f?.name] = f.value;
return acc;
}, {});
components.schemas[schemaId] = generateProperyShape(multipartFormToKeyValue);
components.requestBodies[requestBodyId] = {
content: {
'multipart/form-data:': {
schema: {
$ref: `#/components/schemas/${schemaId}`
}
}
},
description: '',
required: true
};
pathBody['requestBody'] = {
$ref: `#/components/requestBodies/${requestBodyId}`
};
case 'formUrlEncoded':
if (!body?.formUrlEncoded) return;
let formUrlEncodedToKeyValue = body?.formUrlEncoded.reduce((acc, f) => {
acc[f?.name] = f.value;
return acc;
}, {});
components.schemas[schemaId] = generateProperyShape(formUrlEncodedToKeyValue);
components.requestBodies[requestBodyId] = {
content: {
'application/x-www-form-urlencoded:': {
schema: {
$ref: `#/components/schemas/${schemaId}`
}
}
},
description: '',
required: true
};
pathBody['requestBody'] = {
$ref: `#/components/requestBodies/${requestBodyId}`
};
case 'text':
if (!body?.text) return;
pathBody['requestBody'] = {
content: {
'text/plain': {
schema: {
type: 'string'
}
}
}
};
default:
break;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Missing break statements cause fallthrough.

Cases multipartForm, formUrlEncoded, and text all fall through. Additionally, they use return which exits the entire map callback, not just the switch. This corrupts the output for affected items.

         case 'multipartForm':
-          if (!body?.multipartForm) return;
+          if (!body?.multipartForm) break;
           let multipartFormToKeyValue = body?.multipartForm.reduce((acc, f) => {
             // ...
           });
           // ...
+          break;
         case 'formUrlEncoded':
-          if (!body?.formUrlEncoded) return;
+          if (!body?.formUrlEncoded) break;
           let formUrlEncodedToKeyValue = body?.formUrlEncoded.reduce((acc, f) => {
             // ...
           });
           // ...
+          break;
         case 'text':
-          if (!body?.text) return;
+          if (!body?.text) break;
           pathBody['requestBody'] = { /* ... */ };
+          break;
         default:
           break;
📝 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
case 'multipartForm':
if (!body?.multipartForm) return;
let multipartFormToKeyValue = body?.multipartForm.reduce((acc, f) => {
acc[f?.name] = f.value;
return acc;
}, {});
components.schemas[schemaId] = generateProperyShape(multipartFormToKeyValue);
components.requestBodies[requestBodyId] = {
content: {
'multipart/form-data:': {
schema: {
$ref: `#/components/schemas/${schemaId}`
}
}
},
description: '',
required: true
};
pathBody['requestBody'] = {
$ref: `#/components/requestBodies/${requestBodyId}`
};
case 'formUrlEncoded':
if (!body?.formUrlEncoded) return;
let formUrlEncodedToKeyValue = body?.formUrlEncoded.reduce((acc, f) => {
acc[f?.name] = f.value;
return acc;
}, {});
components.schemas[schemaId] = generateProperyShape(formUrlEncodedToKeyValue);
components.requestBodies[requestBodyId] = {
content: {
'application/x-www-form-urlencoded:': {
schema: {
$ref: `#/components/schemas/${schemaId}`
}
}
},
description: '',
required: true
};
pathBody['requestBody'] = {
$ref: `#/components/requestBodies/${requestBodyId}`
};
case 'text':
if (!body?.text) return;
pathBody['requestBody'] = {
content: {
'text/plain': {
schema: {
type: 'string'
}
}
}
};
default:
break;
case 'multipartForm':
if (!body?.multipartForm) break;
let multipartFormToKeyValue = body?.multipartForm.reduce((acc, f) => {
acc[f?.name] = f.value;
return acc;
}, {});
components.schemas[schemaId] = generateProperyShape(multipartFormToKeyValue);
components.requestBodies[requestBodyId] = {
content: {
'multipart/form-data:': {
schema: {
$ref: `#/components/schemas/${schemaId}`
}
}
},
description: '',
required: true
};
pathBody['requestBody'] = {
$ref: `#/components/requestBodies/${requestBodyId}`
};
break;
case 'formUrlEncoded':
if (!body?.formUrlEncoded) break;
let formUrlEncodedToKeyValue = body?.formUrlEncoded.reduce((acc, f) => {
acc[f?.name] = f.value;
return acc;
}, {});
components.schemas[schemaId] = generateProperyShape(formUrlEncodedToKeyValue);
components.requestBodies[requestBodyId] = {
content: {
'application/x-www-form-urlencoded:': {
schema: {
$ref: `#/components/schemas/${schemaId}`
}
}
},
description: '',
required: true
};
pathBody['requestBody'] = {
$ref: `#/components/requestBodies/${requestBodyId}`
};
break;
case 'text':
if (!body?.text) break;
pathBody['requestBody'] = {
content: {
'text/plain': {
schema: {
type: 'string'
}
}
}
};
break;
default:
break;
🧰 Tools
🪛 Biome (2.1.2)

[error] 174-194: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 176-179: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 195-215: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 197-200: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 216-226: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

🤖 Prompt for AI Agents
In packages/bruno-app/src/utils/exporters/openapi-spec.js around lines 174-228,
the switch cases for 'multipartForm', 'formUrlEncoded', and 'text' are missing
break statements and currently use `return` inside each case which exits the
surrounding map callback; change each `if (!body?....) return;` to `if
(!body?....) break;` and add an explicit `break;` at the end of each case after
assigning pathBody (so execution does not fall through to the next case),
ensuring the switch only handles the intended branch and the map callback
continues normally.

components.schemas[schemaId] = generateProperyShape(multipartFormToKeyValue);
components.requestBodies[requestBodyId] = {
content: {
'multipart/form-data:': {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Typo: Trailing colons in content-type keys.

'multipart/form-data:' and 'application/x-www-form-urlencoded:' have erroneous trailing colons, producing invalid OpenAPI specs.

-              'multipart/form-data:': {
+              'multipart/form-data': {
 // ...
-              'application/x-www-form-urlencoded:': {
+              'application/x-www-form-urlencoded': {

Also applies to: 204-204

🤖 Prompt for AI Agents
In packages/bruno-app/src/utils/exporters/openapi-spec.js around lines 183 and
204, the media type keys include stray trailing colons ('multipart/form-data:'
and 'application/x-www-form-urlencoded:'), which yields invalid OpenAPI. Remove
the trailing colons so the keys are exact media types ('multipart/form-data' and
'application/x-www-form-urlencoded'), and run a quick spec validation to confirm
the produced OpenAPI is valid.

Comment on lines +112 to +114
ipcMain.on('main:apispec-opened', (win, pathname, uid, workspacePath = null) => {
watcher.addWatcher(win, pathname, uid, {}, workspacePath);
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

rg -n "emit.*main:apispec-opened" --type=js

Repository: usebruno/bruno

Length of output: 172


🏁 Script executed:

rg -n "main:apispec-opened" --type=js -B 2 -A 2

Repository: usebruno/bruno

Length of output: 172


🌐 Web query:

Electron ipcMain.on callback signature event argument

💡 Result:

The listener signature is:

  • ipcMain.on(channel, (event: IpcMainEvent, ...args: any[]) => void)

The IpcMainEvent (event) provides members you typically use:

  • event.sender — the WebContents that sent the message (use sender.send(...) to reply). [1]
  • event.returnValue — set this to reply for synchronous messages. [1]

(See Electron docs for IpcMainEvent and ipcMain.on.) [1][2]

Sources:
[1] Electron docs — ipcMain (listener signature, event.returnValue, event.sender). [2] Electron docs — ipcRenderer/ipc-main event typing (IpcMainEvent, ...args).


Fix ipcMain.on callback signature — first argument should be event, not win.

The standard Electron ipcMain.on signature is (event: IpcMainEvent, ...args: any[]) => void. The first argument must be an event object that provides event.sender and event.returnValue. The current code incorrectly declares (win, pathname, uid, workspacePath) as parameters. This will cause runtime errors when the event is emitted, as the first argument will be an IpcMainEvent object, not a window.

🤖 Prompt for AI Agents
In packages/bruno-electron/src/ipc/apiSpec.js around lines 112-114, the
ipcMain.on callback currently declares (win, pathname, uid, workspacePath) but
Electron passes an IpcMainEvent as the first argument; change the parameter list
to (event, pathname, uid, workspacePath = null) and use event.sender (or
event.sender.getOwnerBrowserWindow() if a BrowserWindow is needed) when calling
watcher.addWatcher so the watcher receives the actual window/webContents; keep
workspacePath defaulting to null.

@helloanoop helloanoop merged commit dc107f8 into usebruno:main Dec 7, 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