Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ import {
IconSettings,
IconInfoCircle,
IconTerminal2,
IconAppWindow
IconAppWindow,
IconEyeOff
} from '@tabler/icons';
import { useSelector, useDispatch } from 'react-redux';
import { addTab, focusTab, makeTabPermanent } from 'providers/ReduxStore/slices/tabs';
import { handleCollectionItemDrop, sendRequest, showInFolder, pasteItem, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import { toggleCollectionItem, addResponseExample } from 'providers/ReduxStore/slices/collections';
import { handleCollectionItemDrop, sendRequest, showInFolder, pasteItem, saveRequest, updateBrunoConfig } from 'providers/ReduxStore/slices/collections/actions';
import { toggleCollectionItem, addResponseExample, deleteItem } from 'providers/ReduxStore/slices/collections';
import { getCollectionRelativePath, addPathToIgnoreList } from 'utils/collections/ignore';
import { insertTaskIntoQueue } from 'providers/ReduxStore/slices/app';
import { uuid } from 'utils/common';
import { copyRequest, setFocusedSidebarPath } from 'providers/ReduxStore/slices/app';
Expand Down Expand Up @@ -337,6 +339,21 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })

let indents = range(item.depth);

const handleIgnoreFolder = () => {
const relativePath = getCollectionRelativePath(collectionPathname, item.pathname);
if (!relativePath) return;

const brunoConfig = addPathToIgnoreList(collection?.brunoConfig, relativePath);
dispatch(updateBrunoConfig(brunoConfig, collectionUid))
Comment on lines +346 to +347

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

This merge starts from stale brunoConfig.

Line 346 reads collection?.brunoConfig from Redux, but updateBrunoConfig() only persists over IPC and does not update that state slice. Ignoring folder B right after folder A in the same session will rebuild from the pre-change config and can drop A from bruno.json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js`
around lines 346 - 347, The update is rebuilding from a stale collection config
because CollectionItem reads collection?.brunoConfig directly before calling
updateBrunoConfig(). Fix it by using the latest config source in this flow, such
as deriving from the current ignore state or from the result of the previous
update, and ensure addPathToIgnoreList() always merges into the most recent
brunoConfig before dispatching updateBrunoConfig(). This should be adjusted in
the CollectionItem logic where the ignore action is triggered so consecutive
folder ignores cannot overwrite earlier changes.

.then(() => {
// Remove the folder from the sidebar immediately; bruno.json keeps it
// ignored on subsequent loads.
dispatch(deleteItem({ itemUid: item.uid, collectionUid }));
toast.success(`Ignored folder: ${relativePath}`);
Comment on lines +347 to +352

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

Don't use deleteItem to hide an ignored folder.

Line 351 dispatches the same thunk used by the Delete action, and that thunk invokes renderer:delete-item for the folder path. So once the call shape is corrected, Ignore Folder will physically delete the directory instead of just removing it from the tree. The current invocation is also passing { itemUid, collectionUid } even though the thunk signature is (itemUid, collectionUid), so the UI-removal step already rejects today.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js`
around lines 347 - 352, The Ignore Folder flow in CollectionItem should not
reuse the Delete action path, because dispatch(deleteItem(...)) triggers
renderer:delete-item and can physically remove the folder instead of only hiding
it. Update the post-update cleanup in CollectionItem to use the same UI-removal
path as an ignore action, and make sure the call matches the deleteItem thunk
signature only if you keep that helper; otherwise switch to a dedicated
non-destructive tree-removal action after updateBrunoConfig succeeds.

})
.catch(() => toast.error('Failed to ignore folder'));
};

// Build menu items for MenuDropdown
const buildMenuItems = () => {
const items = [];
Expand Down Expand Up @@ -465,6 +482,12 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
const folderCwd = item.pathname || collectionPathname;
await openDevtoolsAndSwitchToTerminal(dispatch, folderCwd);
}
},
{
id: 'ignore-folder',
leftSection: IconEyeOff,
label: 'Ignore Folder',
onClick: handleIgnoreFolder
}
);
}
Expand Down
44 changes: 44 additions & 0 deletions packages/bruno-app/src/utils/collections/ignore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Returns the path of an item relative to its collection root, using forward
* slashes so the value stays portable when written into bruno.json (which can
* be committed and shared across operating systems).
*
* @param {string} collectionPathname absolute path of the collection root
* @param {string} itemPathname absolute path of the item (folder) being ignored
* @returns {string} the collection-relative path, or '' when it cannot be derived
*/
export const getCollectionRelativePath = (collectionPathname, itemPathname) => {
if (!collectionPathname || !itemPathname) return '';

const toPosix = (p) => p.replace(/\\/g, '/').replace(/\/+$/, '');
const root = toPosix(collectionPathname);
const target = toPosix(itemPathname);

if (target === root) return '';
if (target.startsWith(`${root}/`)) {
return target.slice(root.length + 1);
}

return target;
};
Comment on lines +10 to +23

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Return '' when the path is not actually collection-relative.

Line 22 currently falls back to the normalized absolute target path. If itemPathname is outside the collection root, or only differs by case on a case-insensitive filesystem, this writes a machine-specific absolute path into brunoConfig.ignore even though the helper contract says unresolved paths should return ''. The config reader/writer carries ignore entries through as-is, so this would persist a non-portable value into bruno.json. As per path instructions, "Ensure that all code is OS-agnostic" and "Never assume case-sensitive or case-insensitive filesystems."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/utils/collections/ignore.js` around lines 10 - 23, The
fallback in getCollectionRelativePath is returning a normalized absolute target
path when itemPathname is not actually under the collection root. Update the
function so it only returns a relative suffix when target equals root or starts
with root/, and otherwise returns an empty string. Keep the path normalization
in toPosix, but avoid persisting any non-collection-relative value into
brunoConfig.ignore.

Source: Path instructions


/**
* Returns a new brunoConfig with the given path added to its `ignore` list.
* The original config and its ignore array are left untouched. Empty paths and
* paths already present are no-ops.
*
* @param {Object} brunoConfig the collection's bruno.json config
* @param {string} relativePath the collection-relative path to ignore
* @returns {Object} a new brunoConfig with the updated ignore list
*/
export const addPathToIgnoreList = (brunoConfig, relativePath) => {
const config = brunoConfig ? { ...brunoConfig } : {};
const ignore = Array.isArray(config.ignore) ? [...config.ignore] : [];

if (relativePath && !ignore.includes(relativePath)) {
ignore.push(relativePath);
}

config.ignore = ignore;
return config;
};
57 changes: 57 additions & 0 deletions packages/bruno-app/src/utils/collections/ignore.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { getCollectionRelativePath, addPathToIgnoreList } from './ignore';

describe('getCollectionRelativePath', () => {
it('returns the folder path relative to the collection root', () => {
expect(getCollectionRelativePath('/home/adi/my-collection', '/home/adi/my-collection/auth')).toBe('auth');
});

it('returns nested folder paths with forward slashes', () => {
expect(getCollectionRelativePath('/home/adi/my-collection', '/home/adi/my-collection/v1/users')).toBe('v1/users');
});

it('normalizes windows-style separators to forward slashes', () => {
expect(getCollectionRelativePath('C:\\Users\\adi\\coll', 'C:\\Users\\adi\\coll\\auth\\login')).toBe('auth/login');
});

it('tolerates a trailing slash on the collection path', () => {
expect(getCollectionRelativePath('/home/adi/coll/', '/home/adi/coll/auth')).toBe('auth');
});

it('returns an empty string when the item is the collection root', () => {
expect(getCollectionRelativePath('/home/adi/coll', '/home/adi/coll')).toBe('');
});

it('returns an empty string when inputs are missing', () => {
expect(getCollectionRelativePath('', '/home/adi/coll/auth')).toBe('');
expect(getCollectionRelativePath('/home/adi/coll', '')).toBe('');
});
});
Comment on lines +3 to +28

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a regression for non-descendant and case-mismatch inputs.

The helper is documented to return '' when a relative path cannot be derived, but this suite never exercises an item outside the collection root or a case-only root/item mismatch. That leaves the portability edge case unprotected. As per coding guidelines, "Cover both the 'happy path' and the realistically problematic paths." As per path instructions, "Never assume case-sensitive or case-insensitive filesystems."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/utils/collections/ignore.spec.js` around lines 3 - 28,
The getCollectionRelativePath test suite is missing regression coverage for
paths that cannot be derived relative to the collection root. Add cases in
ignore.spec.js for a non-descendant item path and for a case-only mismatch
between the collection root and item path, asserting that
getCollectionRelativePath returns an empty string in both scenarios. Use the
existing getCollectionRelativePath helper to keep the new tests aligned with the
current path-normalization behavior.

Sources: Coding guidelines, Path instructions


describe('addPathToIgnoreList', () => {
it('appends the path to an existing ignore list', () => {
const config = { name: 'coll', ignore: ['node_modules'] };
expect(addPathToIgnoreList(config, 'auth').ignore).toEqual(['node_modules', 'auth']);
});

it('creates the ignore list when the config has none', () => {
expect(addPathToIgnoreList({ name: 'coll' }, 'auth').ignore).toEqual(['auth']);
});

it('does not add duplicates', () => {
const config = { ignore: ['auth'] };
expect(addPathToIgnoreList(config, 'auth').ignore).toEqual(['auth']);
});

it('ignores empty paths', () => {
const config = { ignore: ['auth'] };
expect(addPathToIgnoreList(config, '').ignore).toEqual(['auth']);
});

it('does not mutate the original config or its ignore array', () => {
const config = { ignore: ['node_modules'] };
const next = addPathToIgnoreList(config, 'auth');
expect(config.ignore).toEqual(['node_modules']);
expect(next).not.toBe(config);
expect(next.ignore).not.toBe(config.ignore);
});
});