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
@@ -1,9 +1,10 @@
import React, { useState, useCallback } from 'react';
import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { useTheme } from 'providers/Theme';
import { setCollectionHeaders } from 'providers/ReduxStore/slices/collections';
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
import { updateTableColumnWidths } from 'providers/ReduxStore/slices/tabs';
import SingleLineEditor from 'components/SingleLineEditor';
import EditableTable from 'components/EditableTable';
import StyledWrapper from './StyledWrapper';
Expand All @@ -18,11 +19,21 @@ const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header);
const Headers = ({ collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const tabs = useSelector((state) => state.tabs.tabs);
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
const headers = collection.draft?.root
? get(collection, 'draft.root.request.headers', [])
: get(collection, 'root.request.headers', []);
const [isBulkEditMode, setIsBulkEditMode] = useState(false);

// Get column widths from Redux
const focusedTab = tabs?.find((t) => t.uid === activeTabUid);
const collectionHeadersWidths = focusedTab?.tableColumnWidths?.['collection-headers'] || {};

const handleColumnWidthsChange = (tableId, widths) => {
dispatch(updateTableColumnWidths({ uid: activeTabUid, tableId, widths }));
};

const toggleBulkEditMode = () => {
setIsBulkEditMode(!isBulkEditMode);
};
Expand Down Expand Up @@ -114,11 +125,14 @@ const Headers = ({ collection }) => {
Add request headers that will be sent with every request in this collection.
</div>
<EditableTable
tableId="collection-headers"
columns={columns}
rows={headers}
onChange={handleHeadersChange}
defaultRow={defaultRow}
getRowError={getRowError}
columnWidths={collectionHeadersWidths}
onColumnWidthsChange={(widths) => handleColumnWidthsChange('collection-headers', widths)}
/>
<div className="flex justify-end mt-2">
<button className="text-link select-none" onClick={toggleBulkEditMode}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { useTheme } from 'providers/Theme';
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
import { updateTableColumnWidths } from 'providers/ReduxStore/slices/tabs';
import MultiLineEditor from 'components/MultiLineEditor';
import InfoTip from 'components/InfoTip';
import EditableTable from 'components/EditableTable';
Expand All @@ -13,6 +14,16 @@ import { setCollectionVars } from 'providers/ReduxStore/slices/collections/index
const VarsTable = ({ collection, vars, varType }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const tabs = useSelector((state) => state.tabs.tabs);
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);

// Get column widths from Redux
const focusedTab = tabs?.find((t) => t.uid === activeTabUid);
const collectionVarsWidths = focusedTab?.tableColumnWidths?.['collection-vars'] || {};

const handleColumnWidthsChange = (tableId, widths) => {
dispatch(updateTableColumnWidths({ uid: activeTabUid, tableId, widths }));
};

const onSave = () => dispatch(saveCollectionSettings(collection.uid));

Expand Down Expand Up @@ -68,11 +79,14 @@ const VarsTable = ({ collection, vars, varType }) => {
return (
<StyledWrapper className="w-full">
<EditableTable
tableId="collection-vars"
columns={columns}
rows={vars}
onChange={handleVarsChange}
defaultRow={defaultRow}
getRowError={getRowError}
columnWidths={collectionVarsWidths}
onColumnWidthsChange={(widths) => handleColumnWidthsChange('collection-vars', widths)}
/>
</StyledWrapper>
);
Expand Down
9 changes: 7 additions & 2 deletions packages/bruno-app/src/components/Documentation/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'github-markdown-css/github-markdown.css';
import get from 'lodash/get';
import find from 'lodash/find';
import { updateRequestDocs } from 'providers/ReduxStore/slices/collections';
import { updateDocsEditing } from 'providers/ReduxStore/slices/tabs';
import { useTheme } from 'providers/Theme';
import { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
Expand All @@ -12,12 +14,15 @@ import StyledWrapper from './StyledWrapper';
const Documentation = ({ item, collection }) => {
const dispatch = useDispatch();
const { displayedTheme } = useTheme();
const [isEditing, setIsEditing] = useState(false);
const tabs = useSelector((state) => state.tabs.tabs);
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
const isEditing = focusedTab?.docsEditing || false;
const docs = item.draft ? get(item, 'draft.request.docs') : get(item, 'request.docs');
const preferences = useSelector((state) => state.app.preferences);

const toggleViewMode = () => {
setIsEditing((prev) => !prev);
dispatch(updateDocsEditing({ uid: activeTabUid, docsEditing: !isEditing }));
};

const onEdit = (value) => {
Expand Down
39 changes: 30 additions & 9 deletions packages/bruno-app/src/components/EditableTable/index.js
Copy link
Collaborator

Choose a reason for hiding this comment

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

Either leave it uncontrolled and use refs or leave it controlled and pass it where necessary and handle when the prop isn't passed.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import StyledWrapper from './StyledWrapper';
const MIN_COLUMN_WIDTH = 80;

const EditableTable = ({
tableId, // Not being used kept to maintain uniqueness & pass similar in onColumnWidthsChange
columns,
rows,
onChange,
Expand All @@ -20,21 +21,35 @@ const EditableTable = ({
reorderable = false,
onReorder,
showAddRow = true,
testId = 'editable-table'
testId = 'editable-table',
columnWidths,
onColumnWidthsChange
}) => {
const tableRef = useRef(null);
const emptyRowUidRef = useRef(null);
const [hoveredRow, setHoveredRow] = useState(null);
const [resizing, setResizing] = useState(null);
const [tableHeight, setTableHeight] = useState(0);
const [columnWidths, setColumnWidths] = useState(() => {
const [localColumnWidths, setLocalColumnWidths] = useState(() => {
const initialWidths = {};
columns.forEach((col) => {
initialWidths[col.key] = col.width || 'auto';
});
return initialWidths;
});

// Use controlled props if provided, otherwise use local state
const isControlled = columnWidths !== undefined;
const widths = isControlled ? columnWidths : localColumnWidths;

const handleColumnWidthsChange = useCallback((newWidths) => {
if (isControlled && onColumnWidthsChange) {
onColumnWidthsChange(newWidths);
} else {
setLocalColumnWidths(newWidths);
}
}, [isControlled, onColumnWidthsChange]);

const handleResizeStart = useCallback((e, columnKey) => {
e.preventDefault();
e.stopPropagation();
Expand All @@ -59,11 +74,15 @@ const EditableTable = ({
const maxShrink = startWidth - MIN_COLUMN_WIDTH;
const clampedDiff = Math.max(-maxShrink, Math.min(maxGrow, diff));

setColumnWidths((prev) => ({
...prev,
const newWidths = {
...widths,
[columnKey]: `${startWidth + clampedDiff}px`,
[nextColumnKey]: `${nextColumnStartWidth - clampedDiff}px`
}));
};

if (isControlled) {
handleColumnWidthsChange(newWidths);
}
Comment on lines +83 to +85
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

Uncontrolled mode resize is broken.

handleColumnWidthsChange is only called when isControlled is true. In uncontrolled mode, the resize visually happens but the localColumnWidths state is never updated, so widths reset on re-render.

🐛 Proposed fix
-      if (isControlled) {
-        handleColumnWidthsChange(newWidths);
-      }
+      handleColumnWidthsChange(newWidths);

Apply the same fix at line 111:

-          if (isControlled) {
-            handleColumnWidthsChange({ ...widths, ...newWidths });
-          }
+          handleColumnWidthsChange({ ...widths, ...newWidths });
📝 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
if (isControlled) {
handleColumnWidthsChange(newWidths);
}
handleColumnWidthsChange(newWidths);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/EditableTable/index.js` around lines 83 -
85, The resize handler only calls handleColumnWidthsChange when isControlled is
true, so in uncontrolled mode the localColumnWidths state isn't updated; modify
the branch around isControlled (the block invoking handleColumnWidthsChange) to
also set the internal state when not controlled by calling
setLocalColumnWidths(newWidths) (i.e., if (isControlled) call
handleColumnWidthsChange(newWidths) else call setLocalColumnWidths(newWidths));
update the equivalent location referenced at line ~111 as well so both resize
code paths update localColumnWidths when !isControlled.

};

const handleMouseUp = () => {
Expand All @@ -88,7 +107,9 @@ const EditableTable = ({
});

if (Object.keys(newWidths).length > 0) {
setColumnWidths((prev) => ({ ...prev, ...newWidths }));
if (isControlled) {
handleColumnWidthsChange({ ...widths, ...newWidths });
}
}
}
setResizing(null);
Expand All @@ -98,7 +119,7 @@ const EditableTable = ({

document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}, [columns, showCheckbox]);
}, [columns, showCheckbox, widths, isControlled, handleColumnWidthsChange]);

// Track table height for resize handles
useEffect(() => {
Expand All @@ -118,8 +139,8 @@ const EditableTable = ({
}, [rows.length]);

const getColumnWidth = useCallback((column) => {
return columnWidths[column.key] || column.width || 'auto';
}, [columnWidths]);
return widths[column.key] || column.width || 'auto';
}, [widths]);

const createEmptyRow = useCallback(() => {
const newUid = uuid();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { TableVirtuoso } from 'react-virtuoso';
import cloneDeep from 'lodash/cloneDeep';
import { IconTrash, IconAlertCircle, IconInfoCircle } from '@tabler/icons';
import { useTheme } from 'providers/Theme';
import { useSelector } from 'react-redux';
import { useSelector, useDispatch } from 'react-redux';
import { updateTableColumnWidths } from 'providers/ReduxStore/slices/tabs';
import MultiLineEditor from 'components/MultiLineEditor/index';
import StyledWrapper from './StyledWrapper';
import { uuid } from 'utils/common';
Expand Down Expand Up @@ -55,13 +56,37 @@ const EnvironmentVariablesTable = ({
const valueMatchBg = theme?.colors?.accent ? `${theme.colors.accent}1a` : undefined;
const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments);

const dispatch = useDispatch();
const tabs = useSelector((state) => state.tabs.tabs);
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);

const hasDraftForThisEnv = draft?.environmentUid === environment.uid;

const [tableHeight, setTableHeight] = useState(MIN_H);
const [columnWidths, setColumnWidths] = useState({ name: '30%', value: 'auto' });

// Use environment UID as part of tableId so each environment has its own column widths
const tableId = `env-vars-table-${environment.uid}`;

// Get column widths from Redux - derived value (not state)
const focusedTab = tabs?.find((t) => t.uid === activeTabUid);
const storedColumnWidths = focusedTab?.tableColumnWidths?.[tableId];

// Local state initialized from Redux (computed once on mount/environment change via key)
const [columnWidths, setColumnWidths] = useState(() => {
return storedColumnWidths || { name: '30%', value: 'auto' };
});
Comment on lines +70 to +77
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

Sync columnWidths when the active tab changes.

This state only reads storedColumnWidths on the first render. If the table stays mounted while activeTabUid changes, the previous tab’s widths keep rendering until a remount, so the new per-tab persistence leaks UI state across tabs.

Suggested fix
   const [columnWidths, setColumnWidths] = useState(() => {
     return storedColumnWidths || { name: '30%', value: 'auto' };
   });
+
+  useEffect(() => {
+    setColumnWidths(storedColumnWidths || { name: '30%', value: 'auto' });
+  }, [tableId, storedColumnWidths]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/EnvironmentVariablesTable/index.js` around
lines 69 - 76, The local state columnWidths is only initialized from
storedColumnWidths once, causing widths to persist across tab switches; add a
useEffect that listens for changes to storedColumnWidths (and/or activeTabUid
and tableId) and calls setColumnWidths(storedColumnWidths || { name: '30%',
value: 'auto' }) to sync the local state when the focusedTab or its
tableColumnWidths change, and avoid updating state if the values are equal to
prevent unnecessary re-renders; reference focusedTab, storedColumnWidths,
columnWidths, setColumnWidths, activeTabUid, and tableId.


const [resizing, setResizing] = useState(null);
const [focusedNameIndex, setFocusedNameIndex] = useState(null);

const handleColumnWidthsChange = (id, widths) => {
dispatch(updateTableColumnWidths({ uid: activeTabUid, tableId: id, widths }));
};

// Store column widths in ref for access in event handlers
const columnWidthsRef = useRef(columnWidths);
columnWidthsRef.current = columnWidths;

const handleResizeStart = useCallback((e, columnKey) => {
e.preventDefault();
e.stopPropagation();
Expand All @@ -83,21 +108,24 @@ const EnvironmentVariablesTable = ({
const maxShrink = startWidth - MIN_COLUMN_WIDTH;
const clampedDiff = Math.max(-maxShrink, Math.min(maxGrow, diff));

setColumnWidths({
const newWidths = {
[columnKey]: `${startWidth + clampedDiff}px`,
[nextColumnKey]: `${nextColumnStartWidth - clampedDiff}px`
});
};
setColumnWidths(newWidths);
};

const handleMouseUp = () => {
setResizing(null);
// Save to Redux after resize ends using ref for latest values
handleColumnWidthsChange(tableId, columnWidthsRef.current);
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};

document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}, []);
}, [handleColumnWidthsChange]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing tableId in dependency array.

tableId is used inside handleMouseUp (line 121) but not listed as a dependency. This could cause stale closure issues if tableId changes while a resize is in progress.

-  }, [handleColumnWidthsChange]);
+  }, [handleColumnWidthsChange, tableId]);
📝 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
}, [handleColumnWidthsChange]);
}, [handleColumnWidthsChange, tableId]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/EnvironmentVariablesTable/index.js` at line
128, The useEffect that adds the mouseup listener closes over tableId via the
handleMouseUp logic but only lists handleColumnWidthsChange in its dependency
array; update the dependency array to include tableId (e.g., change "},
[handleColumnWidthsChange]);" to include tableId) so the effect re-runs when
tableId changes and avoid stale closures; if handleMouseUp is defined inline,
consider wrapping it with useCallback or moving its definition so it captures
the latest tableId consistently (refer to handleMouseUp and
handleColumnWidthsChange).


const handleTotalHeightChanged = useCallback((h) => {
setTableHeight(h);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const EnvironmentVariables = ({ environment, setIsModified, collection, searchQu

return (
<EnvironmentVariablesTable
key={environment?.uid}
environment={environment}
collection={collection}
onSave={handleSave}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, { useState, useCallback } from 'react';
import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { useTheme } from 'providers/Theme';
import { setFolderHeaders } from 'providers/ReduxStore/slices/collections';
import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions';
import { updateTableColumnWidths } from 'providers/ReduxStore/slices/tabs';
import SingleLineEditor from 'components/SingleLineEditor';
import EditableTable from 'components/EditableTable';
import StyledWrapper from './StyledWrapper';
Expand All @@ -18,11 +19,21 @@ const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header);
const Headers = ({ collection, folder }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const tabs = useSelector((state) => state.tabs.tabs);
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
const headers = folder.draft
? get(folder, 'draft.request.headers', [])
: get(folder, 'root.request.headers', []);
const [isBulkEditMode, setIsBulkEditMode] = useState(false);

// Get column widths from Redux
const focusedTab = tabs?.find((t) => t.uid === activeTabUid);
const folderHeadersWidths = focusedTab?.tableColumnWidths?.['folder-headers'] || {};

const handleColumnWidthsChange = (tableId, widths) => {
dispatch(updateTableColumnWidths({ uid: activeTabUid, tableId, widths }));
};

const toggleBulkEditMode = () => {
setIsBulkEditMode(!isBulkEditMode);
};
Expand Down Expand Up @@ -119,11 +130,14 @@ const Headers = ({ collection, folder }) => {
Request headers that will be sent with every request inside this folder.
</div>
<EditableTable
tableId="folder-headers"
columns={columns}
rows={headers}
onChange={handleHeadersChange}
defaultRow={defaultRow}
getRowError={getRowError}
columnWidths={folderHeadersWidths}
onColumnWidthsChange={(widths) => handleColumnWidthsChange('folder-headers', widths)}
/>
<div className="flex justify-end mt-2">
<button className="text-link select-none" onClick={toggleBulkEditMode}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { useTheme } from 'providers/Theme';
import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions';
import { updateTableColumnWidths } from 'providers/ReduxStore/slices/tabs';
import MultiLineEditor from 'components/MultiLineEditor';
import InfoTip from 'components/InfoTip';
import EditableTable from 'components/EditableTable';
Expand All @@ -13,6 +14,16 @@ import { setFolderVars } from 'providers/ReduxStore/slices/collections/index';
const VarsTable = ({ folder, collection, vars, varType }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const tabs = useSelector((state) => state.tabs.tabs);
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);

// Get column widths from Redux
const focusedTab = tabs?.find((t) => t.uid === activeTabUid);
const folderVarsWidths = focusedTab?.tableColumnWidths?.['folder-vars'] || {};

const handleColumnWidthsChange = (tableId, widths) => {
dispatch(updateTableColumnWidths({ uid: activeTabUid, tableId, widths }));
};

const onSave = () => dispatch(saveFolderRoot(collection.uid, folder.uid));

Expand Down Expand Up @@ -74,11 +85,14 @@ const VarsTable = ({ folder, collection, vars, varType }) => {
return (
<StyledWrapper className="w-full">
<EditableTable
tableId="folder-vars"
columns={columns}
rows={vars}
onChange={handleVarsChange}
defaultRow={defaultRow}
getRowError={getRowError}
columnWidths={folderVarsWidths}
onColumnWidthsChange={(widths) => handleColumnWidthsChange('folder-vars', widths)}
/>
</StyledWrapper>
);
Expand Down
Loading
Loading