Conversation
WalkthroughAdds end-to-end .env support: UI components (editor, table/raw views, details, collapsible lists), parsing utils, Redux thunks/reducers for collections and workspaces, Electron IPC handlers, a dedicated dotenv watcher, and filesystem helpers; integrates watcher events into the renderer store. (34 words) Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Frontend as Frontend (React)
participant Redux as Redux Store
participant IPC as Electron IPC
participant FS as File System
participant Watcher as DotEnv Watcher
User->>Frontend: Edit .env (table/raw) & Click Save
Frontend->>Redux: dispatch saveDotEnvVariables / saveWorkspaceDotEnvVariables
Redux->>IPC: invoke renderer:save-dotenv-variables / renderer:save-workspace-dotenv-variables
IPC->>FS: write .env file
FS-->>IPC: file written
IPC-->>Redux: resolve promise
Redux-->>Frontend: update state (save success)
Watcher->>FS: chokidar detects file change
Watcher->>Watcher: read & parse .env
Watcher->>IPC: emit main:dotenv-file-update (collection/workspace)
IPC-->>Redux: dispatch setDotEnvVariables / setWorkspaceDotEnvVariables
Redux-->>Frontend: subscribe & refresh UI
sequenceDiagram
participant External as External .env change
participant Watcher as DotEnv Watcher
participant IPC as Electron IPC
participant Redux as Redux Store
participant Frontend as Frontend (React)
External->>Watcher: file add/change/unlink
Watcher->>Watcher: parse and build variables + exists flag
Watcher->>IPC: emit main:dotenv-file-update (payload)
IPC-->>Redux: dispatch setDotEnvVariables / setWorkspaceDotEnvVariables
Redux-->>Frontend: state updated
Frontend-->>User: UI reflects updated .env files
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In `@packages/bruno-app/src/components/Environments/DotEnvFileEditor/index.js`:
- Around line 234-267: The handleRemoveVar callback closes over the whole formik
object but only lists formik.values in its dependency array, causing a stale
closure; update the dependency array to include formik (i.e., use [formik]) so
the hook rebinds when the formik instance changes, and continue to call
formik.setValues(...) inside handleRemoveVar (referencing handleRemoveVar,
formik, and formik.setValues to locate the code).
In
`@packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/index.js`:
- Around line 366-374: Protect against a null/undefined workspace before
accessing workspace.uid in handleSaveDotEnv and handleSaveDotEnvRaw: add a guard
that checks selectedDotEnvFile and workspace (e.g., if (!selectedDotEnvFile ||
!workspace) return Promise.reject(new Error('No file or workspace selected'))),
or use the optional chaining workspace?.uid and reject early if missing, then
call dispatch(saveWorkspaceDotEnvVariables(workspace.uid, ...)) and
dispatch(saveWorkspaceDotEnvRaw(workspace.uid, ...)) only when workspace is
present.
In `@packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js`:
- Around line 2709-2771: The four actions saveDotEnvVariables, saveDotEnvRaw,
createDotEnvFile, and deleteDotEnvFile currently call ipcRenderer but do not
define it; fix by importing/getting ipcRenderer from the global window object
(e.g. const { ipcRenderer } = window) in the same manner other actions in this
module do, ensuring that ipcRenderer is defined before any ipcRenderer.invoke
calls and using that local variable inside each action.
In `@packages/bruno-electron/src/app/dotenv-watcher.js`:
- Around line 79-95: The collection handleUnlink handler currently sends a
'main:dotenv-file-update' message but does not clear the in-memory cached vars;
update handleUnlink to call the equivalent cache-clearing function (e.g.,
clearDotEnvVars or setDotEnvVars with empty values) for the deleted file so the
process-wide cache is cleared (similar to clearWorkspaceDotEnvVars used in the
workspace handler). Import or add the clearDotEnvVars helper if it doesn't
exist, and invoke it with the collectionUid and filename (and any required
params) when isDotEnvFile(filename) is true and the file is deleted, ensuring
the in-memory state matches the sent { exists: false } message.
In `@packages/bruno-electron/src/ipc/collection.js`:
- Around line 569-572: The quoted-value escaping in the block that builds
`${v.name}="..."` fails to escape backslashes, so values like `test\end` become
ambiguous; update the escaping in the routine that computes `escapedValue` (the
variable handling `value` for `v.name`) to first replace backslashes with double
backslashes and then escape double quotes (i.e., perform value.replace(/\\/g,
'\\\\') before value.replace(/"/g, '\\"')) so backslashes are preserved when the
string is re-parsed.
In `@packages/bruno-electron/src/ipc/global-environments.js`:
- Around line 2-3: Validate and canonicalize the renderer-provided filename to
prevent path traversal: restrict accepted names to a basename matching
/^\.env(\.[a-zA-Z0-9_-]+)?$/ (allow only .env or .env.*), ensure you use
path.basename(filename) before any join/resolve, then resolve the resulting path
against the workspace root and verify the resolved path starts with the
workspace directory to prevent escapes; apply this check wherever the filename
variable is used (all functions that read/write/delete dotenv files), replace
direct path.join usage with this safe resolution, and add JSDoc comments to each
dotenv-related abstraction describing expected input (basename .env/.env.*),
validation behavior, and that the function returns a workspace-contained
absolute path.
🧹 Nitpick comments (19)
packages/bruno-app/src/components/Environments/EnvironmentSettings/DeleteDotEnvFile/StyledWrapper.js (1)
4-12: Use theme props instead of CSS variables for colors.Per project standards, styled-components should use theme props for colors rather than CSS variables. The
var(--color-background-danger)should be replaced with a theme-based value.Suggested fix
const Wrapper = styled.div` button.submit { - color: white; - background-color: var(--color-background-danger) !important; + color: ${(props) => props.theme.bg}; + background-color: ${(props) => props.theme.colors.text.danger} !important; border: inherit !important; &:hover { border: inherit !important; } } `;Based on learnings: "Use styled component's theme prop to manage CSS colors, not CSS variables, when in the context of a styled component."
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js (1)
115-133: Note: Duplicate.btn-actionstyling.There's a
.btn-actionblock in.sidebar-header(lines 48-66) withwidth: 24px; height: 24pxand this root-level.btn-actionwithwidth: 22px; height: 22px. If intentional for different contexts, consider adding a comment. Otherwise, this could be consolidated.packages/bruno-app/src/components/Environments/DotEnvFileEditor/StyledWrapper.js (1)
47-62: Consider documenting!importantoverrides.Multiple
!importantdeclarations in the thead styling. If these are needed to override CodeMirror or third-party styles, a brief comment would help future maintainers understand the necessity.packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/StyledWrapper.js (1)
115-133: Duplicate.btn-actionclass with different sizing.There's a
.btn-actiondefined in.sidebar-header(24x24) at lines 48-66 and another standalone (22x22) here. If this is intentional for different contexts, consider renaming one for clarity (e.g.,.btn-action-sm).packages/bruno-app/src/components/Environments/DotEnvFileEditor/index.js (2)
211-232:ErrorMessagecomponent recreated on every render.This inner component is defined inside
DotEnvFileEditorand recreated on each render, causing unnecessary re-renders of tooltip elements.Move outside or memoize
Extract
ErrorMessageoutside the component and passformikas a prop, or useReact.memowith appropriate deps:// Option 1: Move outside component const ErrorMessage = ({ formik, name, index }) => { // ...existing logic using formik passed as prop }; // Then in DotEnvFileEditor, pass formik: <ErrorMessage formik={formik} name={`${index}.name`} index={index} />
269-283:setTimeoutfor deferred state update may cause race conditions.Using
setTimeoutto add a new row after name change could lead to unexpected behavior if the user types quickly or if React batches updates differently.Consider using a callback or effect
const handleNameChange = (index, e) => { formik.handleChange(e); const isLastRow = index === formik.values.length - 1; if (isLastRow) { const newVariable = { uid: uuid(), name: '', value: '' }; - setTimeout(() => { - formik.setFieldValue(formik.values.length, newVariable, false); - }, 0); + // Use setValues to append in the same update cycle + const updatedValues = [...formik.values]; + updatedValues[index] = { ...updatedValues[index], name: e.target.value }; + updatedValues.push(newVariable); + formik.setValues(updatedValues, false); } };Note: This changes the flow slightly - you may need to adjust based on desired UX.
packages/bruno-electron/src/app/dotenv-watcher.js (1)
37-42: InconsistentawaitWriteFinishsettings between collection and workspace watchers.Collection uses
pollInterval: 100while workspace usespollInterval: 250. If intentional (e.g., workspaces update less frequently), a comment would help; otherwise, align them.Also applies to: 126-131
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js (1)
764-782: Redundant Promise wrapper and unuseddispatchparameter.
ipcRenderer.invokealready returns a Promise, so the explicitnew Promise()wrapper is unnecessary. Additionally,dispatchis destructured but never used. This pattern repeats in all four new action creators.Consider refactoring to match the existing async/await pattern used elsewhere in this file (e.g.,
saveWorkspaceDocs):♻️ Suggested refactor
-export const saveWorkspaceDotEnvVariables = (workspaceUid, variables, filename = '.env') => (dispatch, getState) => { - return new Promise((resolve, reject) => { - const state = getState(); - const workspace = state.workspaces.workspaces.find((w) => w.uid === workspaceUid); - - if (!workspace) { - return reject(new Error('Workspace not found')); - } - - if (!workspace.pathname) { - return reject(new Error('Workspace path not found')); - } - - ipcRenderer - .invoke('renderer:save-workspace-dotenv-variables', { workspacePath: workspace.pathname, variables, filename }) - .then(resolve) - .catch(reject); - }); -}; +export const saveWorkspaceDotEnvVariables = (workspaceUid, variables, filename = '.env') => async (dispatch, getState) => { + const state = getState(); + const workspace = state.workspaces.workspaces.find((w) => w.uid === workspaceUid); + + if (!workspace) { + throw new Error('Workspace not found'); + } + + if (!workspace.pathname) { + throw new Error('Workspace path not found'); + } + + return ipcRenderer.invoke('renderer:save-workspace-dotenv-variables', { workspacePath: workspace.pathname, variables, filename }); +};Apply the same refactor to
saveWorkspaceDotEnvRaw,createWorkspaceDotEnvFile, anddeleteWorkspaceDotEnvFile.packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/DotEnvFileDetails/StyledWrapper.js (1)
1-93: Duplicate StyledWrapper exists.This file is identical to
packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/DotEnvFileDetails/StyledWrapper.js. Consider extracting a shared StyledWrapper under a common location (e.g.,components/Environments/Common/) to avoid maintenance burden.packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/DotEnvFileDetails/index.js (1)
1-70: Duplicate component exists.This component is identical to
packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/DotEnvFileDetails/index.js. Consider consolidating into a single shared component undercomponents/Environments/Common/DotEnvFileDetails/to reduce duplication.packages/bruno-app/src/components/Environments/GlobalEnvironmentSettings/index.js (1)
6-11: Remove or use theworkspaceprop being passed toEnvironmentList.The
workspaceprop is passed fromWorkspaceEnvironmentstoEnvironmentList, but it's never destructured or used in the component. Either remove it from the prop chain or use it if needed.packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js (5)
85-98: Stale closure risk in click-outside handler.The
handleCancelDotEnvCreatefunction referenced inside the effect isn't included in the dependency array. IfhandleCancelDotEnvCreatechanges (e.g., due to closures over other state), the effect would reference a stale version.Consider using
useCallbackforhandleCancelDotEnvCreateand adding it to deps, or inline the logic.
203-213:handleDotEnvCloseis defined but never invoked.This function appears to be dead code. If it's intended for future use, consider adding a TODO comment. Otherwise, remove it to avoid confusion.
438-440: Variable shadowing:errorparameter shadows outer scope.The catch block parameter
errorshadows theerrorvariable from the validation check above (line 422). While this works, it can cause confusion during debugging.Rename catch parameter
- .catch((error) => { - toast.error(error.message || 'Failed to create .env file'); + .catch((err) => { + toast.error(err.message || 'Failed to create .env file'); });
462-467: Backspace prevention uses magic number for.envprefix length.The check
input.selectionStart <= 4relies on knowing.envis 4 characters. Consider extracting this to a constant for clarity.Extract constant
+const DOT_ENV_PREFIX = '.env'; +const DOT_ENV_PREFIX_LENGTH = DOT_ENV_PREFIX.length; const handleDotEnvNameKeyDown = (e) => { // ... } else if (e.key === 'Backspace') { const input = e.target; - if (input.selectionStart <= 4 && input.selectionEnd <= 4) { + if (input.selectionStart <= DOT_ENV_PREFIX_LENGTH && input.selectionEnd <= DOT_ENV_PREFIX_LENGTH) { e.preventDefault(); } } };
488-490: Same variable shadowing issue witherrorin catch block.Rename catch parameter
- .catch((error) => { - toast.error(error.message || 'Failed to delete .env file'); + .catch((err) => { + toast.error(err.message || 'Failed to delete .env file'); });packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/index.js (3)
203-213:handleDotEnvCloseis defined but never invoked (same as collection-level component).This appears to be dead code in both files. Consider removing or documenting its intended use.
428-441: Same variable shadowing issues witherrorin catch blocks.Apply the same fix as suggested for the collection-level component.
Also applies to: 470-491
1-788: Extract shared logic from duplicated EnvironmentList components into custom hooks and utilities.Both EnvironmentList components (collection and workspace) are 791/788 lines with identical handler and validator function signatures. Key candidates for extraction:
useClickOutsidehook: Three identical click-outside patterns with boilerplate event listener code can be consolidated into a reusable hookuseDotEnvManagementhook: Duplicated state and handlers for dotenv file operations (creation, deletion, selection, validation)- Environment/DotEnv validation: The
validateEnvironmentNameandvalidateDotEnvNamefunctions are nearly identical and can be sharedNote: Extraction requires abstracting Redux action differences—collection uses collection-scoped actions while workspace uses global/workspace-scoped actions. Design the hooks to accept action creators as parameters.
packages/bruno-app/src/components/Environments/DotEnvFileEditor/index.js
Outdated
Show resolved
Hide resolved
packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/index.js
Show resolved
Hide resolved
packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@packages/bruno-electron/src/app/dotenv-watcher.js`:
- Around line 26-33: The watcher startup in addCollectionWatcher currently
reopens an existing watcher but does not clear any previously cached env values
for that collection; at the start of addCollectionWatcher, reset the
collection's dotenv cache (e.g., delete this.collectionEnvCache[collectionPath]
or set this.collectionEnvCache[collectionPath] = null) so stale .env values
cannot persist when the watcher is restarted; keep this step just before
constructing dotEnvPattern/creating chokidar.watch and use the same
collectionPath key used by collectionWatchers to identify the cache entry to
clear.
🧹 Nitpick comments (1)
packages/bruno-electron/src/utils/filesystem.js (1)
448-514: Add JSDoc to document the dotenv filename validation contract.This helper is already solid; a brief contract note will help callers understand the allowed patterns and basename requirement.
📝 Suggested JSDoc
+/** + * Validates a dotenv filename (".env" or ".env.*") and rejects paths. + * `@param` {string} filename + * `@returns` {boolean} + */ const isValidDotEnvFilename = (filename) => { if (!filename || typeof filename !== 'string') return false; const basename = path.basename(filename); if (basename !== filename) return false; return basename === '.env' || (basename.startsWith('.env.') && /^\.env\.[a-zA-Z0-9._-]+$/.test(basename)); };As per coding guidelines: Add JSDoc comments to abstractions for additional details.
| addCollectionWatcher(win, collectionPath, collectionUid) { | ||
| if (this.collectionWatchers[collectionPath]) { | ||
| this.collectionWatchers[collectionPath].close(); | ||
| } | ||
|
|
||
| const dotEnvPattern = path.join(collectionPath, '.env*'); | ||
|
|
||
| const watcher = chokidar.watch(dotEnvPattern, { |
There was a problem hiding this comment.
Reset collection dotenv cache when (re)starting a watcher.
If a collection was previously watched with a .env and the file is deleted while the watcher is inactive, the cache can retain stale values. Clearing at watcher startup avoids stale process env on reopen.
🧹 Suggested fix
addCollectionWatcher(win, collectionPath, collectionUid) {
+ clearDotEnvVars(collectionUid);
if (this.collectionWatchers[collectionPath]) {
this.collectionWatchers[collectionPath].close();
}🤖 Prompt for AI Agents
In `@packages/bruno-electron/src/app/dotenv-watcher.js` around lines 26 - 33, The
watcher startup in addCollectionWatcher currently reopens an existing watcher
but does not clear any previously cached env values for that collection; at the
start of addCollectionWatcher, reset the collection's dotenv cache (e.g., delete
this.collectionEnvCache[collectionPath] or set
this.collectionEnvCache[collectionPath] = null) so stale .env values cannot
persist when the watcher is restarted; keep this step just before constructing
dotEnvPattern/creating chokidar.watch and use the same collectionPath key used
by collectionWatchers to identify the cache entry to clear.
| @@ -0,0 +1,70 @@ | |||
| import React, { useState } from 'react'; | |||
There was a problem hiding this comment.
@naman-bruno i believe there is an opportunity to extact the this file to components folder and use it everywhere like in packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList , packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList
| expanded={environmentsExpanded} | ||
| onToggle={() => setEnvironmentsExpanded(!environmentsExpanded)} | ||
| actions={( | ||
| <> |
There was a problem hiding this comment.
https://github.com/user-attachments/assets/b5c1ad7c-a53e-4858-8713-2b93fe51fce8 deleting a single entry deletes the entire values
| validationSchema: Yup.array().of( | ||
| Yup.object({ | ||
| name: Yup.string() | ||
| .when('$isLastRow', { |
There was a problem hiding this comment.
$isLastRow i don't see Yup context variable being passed, probably this is redundant, we can just keep the validate function
| } | ||
| }, [formik.values, savedValuesJson, setIsModified, viewMode, rawValue, rawContent]); | ||
|
|
||
| const ErrorMessage = ({ name, index }) => { |
There was a problem hiding this comment.
maybe we can move the component outside so that it is not re-created on every re-render
// Outside DotEnvFileEditor
const ErrorMessage = React.memo(({ formik, name, index }) => {
const meta = formik.getFieldMeta(name);
const id = `error-${name}-${index}`;
const isLastRow = index === formik.values.length - 1;
const variable = formik.values[index];
const isEmptyRow = !variable?.name || variable.name.trim() === '';
if ((isLastRow && isEmptyRow) || !meta.error || !meta.touched) {
return null;
}
return (
<span>
<IconAlertCircle id={id} className="text-red-600 cursor-pointer" size={20} />
<Tooltip className="tooltip-mod" anchorId={id} html={meta.error || ''} />
</span>
);
});
// Usage inside DotEnvFileEditor:
<ErrorMessage formik={formik} name={`${index}.name`} index={index} />| ); | ||
| }; | ||
|
|
||
| const handleRemoveVar = useCallback((id) => { |
There was a problem hiding this comment.
if i am not wrong formik.values is a new object on every render, so this useCallback never memoizes effectively.
should we use ref?
const valuesRef = useRef(formik.values);
valuesRef.current = formik.values;
const handleRemoveVar = useCallback((id) => {
const currentValues = valuesRef.current;
if (!currentValues || currentValues.length === 0) return;
const lastRow = currentValues[currentValues.length - 1];
const isLastEmptyRow = lastRow?.uid === id && (!lastRow.name || lastRow.name.trim() === '');
if (isLastEmptyRow) return;
const filteredValues = currentValues.filter((v) => v.uid !== id);
const hasEmptyLastRow = filteredValues.length > 0 &&
(!filteredValues[filteredValues.length - 1].name ||
filteredValues[filteredValues.length - 1].name.trim() === '');
const newValues = hasEmptyLastRow
? filteredValues
: [...filteredValues, { uid: uuid(), name: '', value: '' }];
formik.setValues(newValues);
}, []);| name: '', | ||
| value: '' | ||
| }; | ||
| setTimeout(() => { |
There was a problem hiding this comment.
setTimeout captures stale formik.values.length, The length is captured at closure time, not execution time, using setTimeout here feels a bit odd, do we really need this?
or should we do something like this?
const handleNameChange = (index, e) => {
formik.handleChange(e);
const isLastRow = index === formik.values.length - 1;
if (isLastRow) {
const newVariable = { uid: uuid(), name: '', value: '' };
// Use functional approach via setValues instead
setTimeout(() => {
formik.setValues((prev) => {
const lastRow = prev[prev.length - 1];
// Only add if last row still has content
if (lastRow?.name?.trim()) {
return [...prev, newVariable];
}
return prev;
});
}, 0);
}
};another alternative
const handleNameChange = (index, e) => {
formik.handleChange(e);
const isLastRow = index === formik.values.length - 1;
if (isLastRow) {
const newVariable = { uid: uuid(), name: '', value: '' };
setTimeout(() => {
const currentLength = valuesRef.current.length;
formik.setFieldValue(currentLength, newVariable, false);
}, 0);
}
};| } | ||
| }; | ||
|
|
||
| const handleSave = () => { |
There was a problem hiding this comment.
should we consider any loading state for saving things? users can click save multiple time while pending a save.
| setDotEnvExpanded(true); | ||
| }; | ||
|
|
||
| const handleDotEnvClose = () => { |
There was a problem hiding this comment.
handleDotEnvClose is defined but never used
| onToggle={() => setEnvironmentsExpanded(!environmentsExpanded)} | ||
| actions={( | ||
| <> | ||
| <button className="btn-action" onClick={() => handleCreateEnvClick()} title="Create environment"> |
There was a problem hiding this comment.
All action buttons missing type="button".
<button
type="button"
className="btn-action"
onClick={handleCreateEnvClick}
title="Create environment"
aria-label="Create environment"
>| <div | ||
| key={env.uid} | ||
| id={env.uid} | ||
| className={`environment-item ${activeView === 'environment' && selectedEnvironment?.uid === env.uid ? 'active' : ''} ${renamingEnvUid === env.uid ? 'renaming' : ''} ${activeEnvironmentUid === env.uid ? 'activated' : ''}`} |
There was a problem hiding this comment.
Long className strings
className={classNames('environment-item', {
active: activeView === 'environment' && selectedEnvironment?.uid === env.uid,
renaming: renamingEnvUid === env.uid,
activated: activeEnvironmentUid === env.uid
})}|
|
||
| const selectedDotEnvData = dotEnvFiles.find((f) => f.filename === selectedDotEnvFile); | ||
|
|
||
| const renderContent = () => { |
There was a problem hiding this comment.
renderContent could be memoized
const content = useMemo(() => {
if (activeView === 'dotenv' && selectedDotEnvFile && selectedDotEnvData) {
return (
<DotEnvFileDetails
title={selectedDotEnvFile}
onDelete={() => handleDeleteDotEnvFile(selectedDotEnvFile)}
dotEnvExists={selectedDotEnvData?.exists}
viewMode={dotEnvViewMode}
onViewModeChange={handleDotEnvViewModeChange}
>
<DotEnvFileEditor
variables={selectedDotEnvData?.variables || []}
onSave={handleSaveDotEnv}
onSaveRaw={handleSaveDotEnvRaw}
isModified={isDotEnvModified}
setIsModified={setIsDotEnvModified}
dotEnvExists={selectedDotEnvData?.exists}
viewMode={dotEnvViewMode}
/>
</DotEnvFileDetails>
);
}
if (selectedEnvironment) {
return (
<EnvironmentDetails
environment={selectedEnvironment}
setIsModified={setIsModified}
originalEnvironmentVariables={originalEnvironmentVariables}
collection={collection}
/>
);
}
return (
<EmptyState
onCreateClick={handleCreateEnvClick}
onImportClick={handleImportClick}
/>
);
}, [
activeView,
selectedDotEnvFile,
selectedDotEnvData,
dotEnvViewMode,
isDotEnvModified,
selectedEnvironment,
originalEnvironmentVariables,
collection
]);
// Then in JSX:
{content}| @@ -1,22 +1,40 @@ | |||
| import React, { useEffect, useState, useRef } from 'react'; | |||
There was a problem hiding this comment.
Can be done at a later stage, i guess there is merit in refactoring the filestructure when it comes to environmentList within collection and workspace
### **Recommended refactor structure:**
components/Environments/
├── EnvironmentList/
│ ├── index.jsx // Shared base component
│ ├── CollectionEnvironmentList.jsx // Thin wrapper with collection actions
│ ├── WorkspaceEnvironmentList.jsx // Thin wrapper with workspace actions
│ ├── EnvironmentItem.jsx // Single item component
│ ├── DotEnvSection.jsx // DotEnv files section
│ ├── InlineCreateInput.jsx // Shared inline input
│ ├── EmptyState.jsx // Empty state component
│ └── hooks/
│ ├── useEnvironmentSelection.js
│ ├── useDotEnvFiles.js
│ ├── useInlineEdit.js
│ └── useClickOutside.js|
@naman-bruno would be nice if we have playwright test cases verifying the intended behaviours like addition, deletion env files, env rows etc, if time permits |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@packages/bruno-app/src/components/Environments/DotEnvFileEditor/index.js`:
- Around line 567-572: The Save and Reset buttons in DotEnvFileEditor are
missing the disabled prop, which allows double submissions; update the two
buttons that call handleSave and handleReset to include disabled={isSaving}
(consistent with the raw-mode and empty-state buttons) so they are disabled
while the isSaving flag is true; ensure you reference the same isSaving state
used elsewhere in the component and apply the change to the buttons rendered in
the table/normal mode.
- Line 83: The unescaping order in the DotEnvFileEditor (the line that assigns
to variable `value` using chained .replace calls) is wrong: it turns literal
`\\n` into a newline. Fix by unescaping backslashes last (or use a placeholder
strategy) so `\\\\` → `\\` is applied after `\\n` and `\\"`; update the replace
sequence on `value` (or implement a placeholder-replace/unreplace pass) to
ensure `\\n` remains a literal `\n` only when intended and that `\\\\` is
handled last.
In
`@packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/index.js`:
- Line 425: In handleSaveNewDotEnv, avoid unguarded access to workspace.uid
before dispatching createWorkspaceDotEnvFile; add a null/undefined guard (e.g.,
if (!workspace || !workspace.uid) return or disable the path) so you only call
dispatch(createWorkspaceDotEnvFile(workspace.uid, newDotEnvName)) when
workspace.uid exists, and surface a user-facing error or early return otherwise;
update references in that handler and any related save flow to use the guarded
check.
- Line 468: handleDeleteDotEnvFile calls
dispatch(deleteWorkspaceDotEnvFile(workspace.uid, filename)) without ensuring
workspace exists; add a guard to avoid accessing workspace.uid when workspace is
null/undefined. Update handleDeleteDotEnvFile to check for workspace (e.g., if
(!workspace || !workspace.uid) return or show an error/toast) before calling
deleteWorkspaceDotEnvFile, or use optional chaining and handle the undefined
case so dispatch is only invoked with a valid workspace.uid.
🧹 Nitpick comments (3)
packages/bruno-app/src/components/Environments/DotEnvFileEditor/index.js (1)
421-494: Code duplication between empty-state and normal table rendering.The table rendering logic at lines 431-482 is nearly identical to lines 499-563, except the empty-state table only shows the Name column. Consider extracting a shared table component or consolidating the rendering paths.
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js (1)
50-65: Large component with many state variables.This component manages 11+ new state variables for dotenv handling. Per coding standards, consider extracting dotenv-related state and logic into a custom hook (e.g.,
useDotEnvManagement) to improve maintainability and testability.packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/index.js (1)
24-34: Significant code duplication withEnvironmentSettings/EnvironmentList/index.js.This component shares ~90% of its logic with the collection-level EnvironmentList. Consider extracting shared logic into a common component or hook that accepts configuration for the data source (collection vs. workspace) and corresponding actions.
packages/bruno-app/src/components/Environments/DotEnvFileEditor/index.js
Outdated
Show resolved
Hide resolved
packages/bruno-app/src/components/Environments/DotEnvFileEditor/index.js
Outdated
Show resolved
Hide resolved
| return; | ||
| } | ||
|
|
||
| dispatch(createWorkspaceDotEnvFile(workspace.uid, newDotEnvName)) |
There was a problem hiding this comment.
Unguarded workspace.uid access in handleSaveNewDotEnv.
Same issue as the save handlers — workspace.uid is accessed without null check.
Proposed fix
const handleSaveNewDotEnv = () => {
const error = validateDotEnvName(newDotEnvName);
if (error) {
setDotEnvNameError(error);
return;
}
+ if (!workspace?.uid) {
+ toast.error('Workspace not available');
+ return;
+ }
+
dispatch(createWorkspaceDotEnvFile(workspace.uid, newDotEnvName))🤖 Prompt for AI Agents
In
`@packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/index.js`
at line 425, In handleSaveNewDotEnv, avoid unguarded access to workspace.uid
before dispatching createWorkspaceDotEnvFile; add a null/undefined guard (e.g.,
if (!workspace || !workspace.uid) return or disable the path) so you only call
dispatch(createWorkspaceDotEnvFile(workspace.uid, newDotEnvName)) when
workspace.uid exists, and surface a user-facing error or early return otherwise;
update references in that handler and any related save flow to use the guarded
check.
| }; | ||
|
|
||
| const handleDeleteDotEnvFile = (filename) => { | ||
| dispatch(deleteWorkspaceDotEnvFile(workspace.uid, filename)) |
There was a problem hiding this comment.
Unguarded workspace.uid access in handleDeleteDotEnvFile.
Add a null check before accessing workspace.uid.
Proposed fix
const handleDeleteDotEnvFile = (filename) => {
+ if (!workspace?.uid) {
+ toast.error('Workspace not available');
+ return;
+ }
dispatch(deleteWorkspaceDotEnvFile(workspace.uid, filename))🤖 Prompt for AI Agents
In
`@packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/index.js`
at line 468, handleDeleteDotEnvFile calls
dispatch(deleteWorkspaceDotEnvFile(workspace.uid, filename)) without ensuring
workspace exists; add a guard to avoid accessing workspace.uid when workspace is
null/undefined. Update handleDeleteDotEnvFile to check for workspace (e.g., if
(!workspace || !workspace.uid) return or show an error/toast) before calling
deleteWorkspaceDotEnvFile, or use optional chaining and handle the undefined
case so dispatch is only invoked with a valid workspace.uid.
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Fix all issues with AI agents
In `@packages/bruno-app/src/components/Environments/DotEnvFileDetails/index.js`:
- Around line 51-53: The delete button is icon-only and needs an accessible
label; update the button element rendered in DotEnvFileDetails (the button with
onClick={handleDeleteClick} and className "action-btn delete-btn") to include an
aria-label (e.g., aria-label="Delete .env file") so screen readers announce its
purpose; keep the existing title and IconTrash but add the aria-label attribute
on that button.
In
`@packages/bruno-app/src/components/Environments/DotEnvFileEditor/DotEnvTableView.js`:
- Around line 8-10: Replace the unstable row test id that uses item.name with
the stable UID: in the TableRow component update the data-testid value from
`dotenv-var-row-${item.name}` to `dotenv-var-row-${item.uid}` (the component
already uses item.uid as the key), and update any tests/selectors that relied on
the name-based test id to use the uid-based id instead.
In `@packages/bruno-app/src/components/Environments/DotEnvFileEditor/index.js`:
- Around line 76-119: The dirty-tracking and reset logic treat raw baseline as
'' when rawContent is undefined which causes raw mode to always appear modified
and resets to empty; fix by deriving a single baselineRaw (e.g., const
baselineRaw = rawContent ?? variablesToRaw(variables || [])) and use that
baseline in both the view-mode switch (where you build newRawValue) and in the
modification check (replace rawContent || '' with baselineRaw) as well as in the
other similar block around lines 248-261; update uses of
rawContent/variablesToRaw/rawValue comparisons to reference baselineRaw and
ensure setRawValue and setIsModified use that same derived baseline.
In
`@packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js`:
- Around line 575-585: These icon-only action buttons (the JSX buttons that call
handleCreateEnvClick, handleImportClick, and handleExportClick) lack aria-label
attributes; update each button to include a descriptive aria-label (e.g.,
"Create environment", "Import environment", "Export environment") so screen
readers can announce their purpose, and apply the same change to the other
icon-only buttons around the 705–711 block to ensure consistent accessibility
across EnvironmentList.
- Around line 72-82: When dotEnvFiles becomes empty the useEffect handling
selection should also clear dotenv-related UI state; inside the existing
useEffect (referencing dotEnvFiles, selectedDotEnvFile, setSelectedDotEnvFile)
add logic in the dotEnvFiles.length === 0 branch to call
setSelectedDotEnvFile(null) (already present) and also reset the dotenv view
state by calling setActiveView(...) to a non-"dotenv" view (e.g. null or the
default list view) and call setIsDotEnvModified(false) to clear the modified
flag so navigation is not blocked; update the effect to import/use setActiveView
and setIsDotEnvModified where needed.
In
`@packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/index.js`:
- Around line 572-581: The icon-only action buttons (the three buttons invoking
handleCreateEnvClick, handleImportClick, and handleExportClick in
EnvironmentList) lack accessible labels; add descriptive aria-label attributes
(e.g., aria-label="Create environment", aria-label="Import environment",
aria-label="Export environment") to each corresponding <button> so screen
readers can announce their purpose, and apply the same change to the other
icon-only group referenced around the later block (the buttons at the 702-708
region).
- Around line 72-82: The effect that resets dotenv selection doesn't run when
selectedDotEnvFile changes, so add selectedDotEnvFile to the dependency list and
ensure the early-empty check still sets setSelectedDotEnvFile(null) and returns;
update the useEffect signature in EnvironmentList to use [dotEnvFiles,
selectedDotEnvFile] so the logic that checks dotEnvFiles.length, computes
fileExists (dotEnvFiles.some(...)), and sets
setSelectedDotEnvFile(dotEnvFiles[0].filename) runs correctly on both list-sync
and selection changes.
In `@packages/bruno-electron/src/app/dotenv-watcher.js`:
- Around line 149-172: When adding a watcher in addWorkspaceWatcher, clear any
cached workspace dotenv state before (re)creating the chokidar watcher to avoid
stale vars after a deleted .env; call clearWorkspaceDotEnvVars(workspacePath)
(or the same clearEnvVars used in handlerOptions) right after closing an
existing watcher and before initiating chokidar.watch so the workspace dotenv
cache is reset prior to re-watching.
🧹 Nitpick comments (1)
packages/bruno-electron/src/app/dotenv-watcher.js (1)
7-18: Add JSDoc for the new abstractions.Please add brief JSDoc for the helper utilities, handler factories, and the
DotEnvWatcherclass so parameter/return contracts are clear.As per coding guidelines, "Add JSDoc comments to abstractions for additional details".💡 Example JSDoc additions
+/** + * Creates a handler for dotenv file add/change events. + * `@param` {object} win + * `@param` {object} options + * `@returns` {Function} + */ const createFileHandler = (win, options) => (pathname) => {+/** + * Watches collection/workspace dotenv files and emits IPC updates. + */ class DotEnvWatcher {Also applies to: 27-90, 92-210
| <button type="button" onClick={handleDeleteClick} title="Delete .env file" className="action-btn delete-btn"> | ||
| <IconTrash size={15} strokeWidth={1.5} /> | ||
| </button> |
There was a problem hiding this comment.
Add an aria-label to the icon-only delete button.
Title alone isn’t reliably announced by screen readers.
Suggested fix
- <button type="button" onClick={handleDeleteClick} title="Delete .env file" className="action-btn delete-btn">
+ <button type="button" onClick={handleDeleteClick} title="Delete .env file" aria-label="Delete .env file" className="action-btn delete-btn">
<IconTrash size={15} strokeWidth={1.5} />
</button>📝 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.
| <button type="button" onClick={handleDeleteClick} title="Delete .env file" className="action-btn delete-btn"> | |
| <IconTrash size={15} strokeWidth={1.5} /> | |
| </button> | |
| <button type="button" onClick={handleDeleteClick} title="Delete .env file" aria-label="Delete .env file" className="action-btn delete-btn"> | |
| <IconTrash size={15} strokeWidth={1.5} /> | |
| </button> |
🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/Environments/DotEnvFileDetails/index.js`
around lines 51 - 53, The delete button is icon-only and needs an accessible
label; update the button element rendered in DotEnvFileDetails (the button with
onClick={handleDeleteClick} and className "action-btn delete-btn") to include an
aria-label (e.g., aria-label="Delete .env file") so screen readers announce its
purpose; keep the existing title and IconTrash but add the aria-label attribute
on that button.
| const TableRow = React.memo(({ children, item }) => ( | ||
| <tr key={item.uid} data-testid={`dotenv-var-row-${item.name}`}>{children}</tr> | ||
| ), (prevProps, nextProps) => { |
There was a problem hiding this comment.
Use UID in row test id to avoid duplicates.
item.name can repeat or be empty, which makes selectors ambiguous. Prefer the stable UID.
✅ Suggested fix
-const TableRow = React.memo(({ children, item }) => (
- <tr key={item.uid} data-testid={`dotenv-var-row-${item.name}`}>{children}</tr>
+const TableRow = React.memo(({ children, item }) => (
+ <tr key={item.uid} data-testid={`dotenv-var-row-${item.uid}`}>{children}</tr>📝 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.
| const TableRow = React.memo(({ children, item }) => ( | |
| <tr key={item.uid} data-testid={`dotenv-var-row-${item.name}`}>{children}</tr> | |
| ), (prevProps, nextProps) => { | |
| const TableRow = React.memo(({ children, item }) => ( | |
| <tr key={item.uid} data-testid={`dotenv-var-row-${item.uid}`}>{children}</tr> | |
| ), (prevProps, nextProps) => { |
🤖 Prompt for AI Agents
In
`@packages/bruno-app/src/components/Environments/DotEnvFileEditor/DotEnvTableView.js`
around lines 8 - 10, Replace the unstable row test id that uses item.name with
the stable UID: in the TableRow component update the data-testid value from
`dotenv-var-row-${item.name}` to `dotenv-var-row-${item.uid}` (the component
already uses item.uid as the key), and update any tests/selectors that relied on
the name-based test id to use the uid-based id instead.
packages/bruno-app/src/components/Environments/DotEnvFileEditor/index.js
Outdated
Show resolved
Hide resolved
| <> | ||
| <button type="button" className="btn-action" onClick={() => handleCreateEnvClick()} title="Create environment"> | ||
| <IconPlus size={14} strokeWidth={1.5} /> | ||
| </button> | ||
| <button type="button" className="btn-action" onClick={() => handleImportClick()} title="Import environment"> | ||
| <IconDownload size={14} strokeWidth={1.5} /> | ||
| </button> | ||
| <button type="button" className="btn-action" onClick={() => handleExportClick()} title="Export environment"> | ||
| <IconUpload size={14} strokeWidth={1.5} /> | ||
| </button> | ||
| </> |
There was a problem hiding this comment.
Add aria-labels to icon-only section actions.
These buttons are icon-only; add aria-label for accessibility.
Suggested fix
- <button type="button" className="btn-action" onClick={() => handleCreateEnvClick()} title="Create environment">
+ <button type="button" className="btn-action" onClick={() => handleCreateEnvClick()} title="Create environment" aria-label="Create environment">
<IconPlus size={14} strokeWidth={1.5} />
</button>
- <button type="button" className="btn-action" onClick={() => handleImportClick()} title="Import environment">
+ <button type="button" className="btn-action" onClick={() => handleImportClick()} title="Import environment" aria-label="Import environment">
<IconDownload size={14} strokeWidth={1.5} />
</button>
- <button type="button" className="btn-action" onClick={() => handleExportClick()} title="Export environment">
+ <button type="button" className="btn-action" onClick={() => handleExportClick()} title="Export environment" aria-label="Export environment">
<IconUpload size={14} strokeWidth={1.5} />
</button>- <button
- className="btn-action"
- onClick={handleCreateDotEnvInlineClick}
- title="Create .env file"
- >
+ <button
+ className="btn-action"
+ onClick={handleCreateDotEnvInlineClick}
+ title="Create .env file"
+ aria-label="Create .env file"
+ >
<IconPlus size={14} strokeWidth={1.5} />
</button>Also applies to: 705-711
🤖 Prompt for AI Agents
In
`@packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js`
around lines 575 - 585, These icon-only action buttons (the JSX buttons that
call handleCreateEnvClick, handleImportClick, and handleExportClick) lack
aria-label attributes; update each button to include a descriptive aria-label
(e.g., "Create environment", "Import environment", "Export environment") so
screen readers can announce their purpose, and apply the same change to the
other icon-only buttons around the 705–711 block to ensure consistent
accessibility across EnvironmentList.
| useEffect(() => { | ||
| if (dotEnvFiles.length === 0) { | ||
| setSelectedDotEnvFile(null); | ||
| return; | ||
| } | ||
|
|
||
| const fileExists = dotEnvFiles.some((f) => f.filename === selectedDotEnvFile); | ||
| if (!selectedDotEnvFile || !fileExists) { | ||
| setSelectedDotEnvFile(dotEnvFiles[0].filename); | ||
| } | ||
| }, [dotEnvFiles]); |
There was a problem hiding this comment.
Reset dotenv view state when the list becomes empty.
Same edge case as collection view—reset dotenv state when the list is cleared via sync.
Suggested fix
useEffect(() => {
if (dotEnvFiles.length === 0) {
setSelectedDotEnvFile(null);
+ setIsDotEnvModified(false);
+ setActiveView('environment');
return;
}🤖 Prompt for AI Agents
In
`@packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/index.js`
around lines 72 - 82, The effect that resets dotenv selection doesn't run when
selectedDotEnvFile changes, so add selectedDotEnvFile to the dependency list and
ensure the early-empty check still sets setSelectedDotEnvFile(null) and returns;
update the useEffect signature in EnvironmentList to use [dotEnvFiles,
selectedDotEnvFile] so the logic that checks dotEnvFiles.length, computes
fileExists (dotEnvFiles.some(...)), and sets
setSelectedDotEnvFile(dotEnvFiles[0].filename) runs correctly on both list-sync
and selection changes.
| <> | ||
| <button type="button" className="btn-action" onClick={() => handleCreateEnvClick()} title="Create environment"> | ||
| <IconPlus size={14} strokeWidth={1.5} /> | ||
| </button> | ||
| <button type="button" className="btn-action" onClick={() => handleImportClick()} title="Import environment"> | ||
| <IconDownload size={14} strokeWidth={1.5} /> | ||
| </button> | ||
| <button type="button" className="btn-action" onClick={() => handleExportClick()} title="Export environment"> | ||
| <IconUpload size={14} strokeWidth={1.5} /> | ||
| </button> |
There was a problem hiding this comment.
Add aria-labels to icon-only section actions.
Improves screen reader support.
Suggested fix
- <button type="button" className="btn-action" onClick={() => handleCreateEnvClick()} title="Create environment">
+ <button type="button" className="btn-action" onClick={() => handleCreateEnvClick()} title="Create environment" aria-label="Create environment">
<IconPlus size={14} strokeWidth={1.5} />
</button>
- <button type="button" className="btn-action" onClick={() => handleImportClick()} title="Import environment">
+ <button type="button" className="btn-action" onClick={() => handleImportClick()} title="Import environment" aria-label="Import environment">
<IconDownload size={14} strokeWidth={1.5} />
</button>
- <button type="button" className="btn-action" onClick={() => handleExportClick()} title="Export environment">
+ <button type="button" className="btn-action" onClick={() => handleExportClick()} title="Export environment" aria-label="Export environment">
<IconUpload size={14} strokeWidth={1.5} />
</button>- <button
- className="btn-action"
- onClick={handleCreateDotEnvInlineClick}
- title="Create .env file"
- >
+ <button
+ className="btn-action"
+ onClick={handleCreateDotEnvInlineClick}
+ title="Create .env file"
+ aria-label="Create .env file"
+ >
<IconPlus size={14} strokeWidth={1.5} />
</button>Also applies to: 702-708
🤖 Prompt for AI Agents
In
`@packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/index.js`
around lines 572 - 581, The icon-only action buttons (the three buttons invoking
handleCreateEnvClick, handleImportClick, and handleExportClick in
EnvironmentList) lack accessible labels; add descriptive aria-label attributes
(e.g., aria-label="Create environment", aria-label="Import environment",
aria-label="Export environment") to each corresponding <button> so screen
readers can announce their purpose, and apply the same change to the other
icon-only group referenced around the later block (the buttons at the 702-708
region).
| addWorkspaceWatcher(win, workspacePath, workspaceUid) { | ||
| if (this.workspaceWatchers.has(workspacePath)) { | ||
| this.workspaceWatchers.get(workspacePath).close(); | ||
| } | ||
|
|
||
| const dotEnvPattern = path.join(workspacePath, '.env*'); | ||
|
|
||
| const watcher = chokidar.watch(dotEnvPattern, { | ||
| ...DEFAULT_WATCHER_OPTIONS, | ||
| awaitWriteFinish: { | ||
| stabilityThreshold: 80, | ||
| pollInterval: 250 | ||
| } | ||
| }); | ||
|
|
||
| const handlerOptions = { | ||
| type: 'workspace', | ||
| uid: workspaceUid, | ||
| uidKey: 'workspaceUid', | ||
| pathKey: 'workspacePath', | ||
| basePath: workspacePath, | ||
| setEnvVars: (data) => setWorkspaceDotEnvVars(workspacePath, data), | ||
| clearEnvVars: () => clearWorkspaceDotEnvVars(workspacePath) | ||
| }; |
There was a problem hiding this comment.
Reset workspace dotenv cache before re-watching.
If the watcher is restarted after .env is deleted, cached workspace vars can remain stale. Clearing on startup avoids that edge case.
🧹 Suggested fix
addWorkspaceWatcher(win, workspacePath, workspaceUid) {
+ clearWorkspaceDotEnvVars(workspacePath);
if (this.workspaceWatchers.has(workspacePath)) {
this.workspaceWatchers.get(workspacePath).close();
}📝 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.
| addWorkspaceWatcher(win, workspacePath, workspaceUid) { | |
| if (this.workspaceWatchers.has(workspacePath)) { | |
| this.workspaceWatchers.get(workspacePath).close(); | |
| } | |
| const dotEnvPattern = path.join(workspacePath, '.env*'); | |
| const watcher = chokidar.watch(dotEnvPattern, { | |
| ...DEFAULT_WATCHER_OPTIONS, | |
| awaitWriteFinish: { | |
| stabilityThreshold: 80, | |
| pollInterval: 250 | |
| } | |
| }); | |
| const handlerOptions = { | |
| type: 'workspace', | |
| uid: workspaceUid, | |
| uidKey: 'workspaceUid', | |
| pathKey: 'workspacePath', | |
| basePath: workspacePath, | |
| setEnvVars: (data) => setWorkspaceDotEnvVars(workspacePath, data), | |
| clearEnvVars: () => clearWorkspaceDotEnvVars(workspacePath) | |
| }; | |
| addWorkspaceWatcher(win, workspacePath, workspaceUid) { | |
| clearWorkspaceDotEnvVars(workspacePath); | |
| if (this.workspaceWatchers.has(workspacePath)) { | |
| this.workspaceWatchers.get(workspacePath).close(); | |
| } | |
| const dotEnvPattern = path.join(workspacePath, '.env*'); | |
| const watcher = chokidar.watch(dotEnvPattern, { | |
| ...DEFAULT_WATCHER_OPTIONS, | |
| awaitWriteFinish: { | |
| stabilityThreshold: 80, | |
| pollInterval: 250 | |
| } | |
| }); | |
| const handlerOptions = { | |
| type: 'workspace', | |
| uid: workspaceUid, | |
| uidKey: 'workspaceUid', | |
| pathKey: 'workspacePath', | |
| basePath: workspacePath, | |
| setEnvVars: (data) => setWorkspaceDotEnvVars(workspacePath, data), | |
| clearEnvVars: () => clearWorkspaceDotEnvVars(workspacePath) | |
| }; |
🤖 Prompt for AI Agents
In `@packages/bruno-electron/src/app/dotenv-watcher.js` around lines 149 - 172,
When adding a watcher in addWorkspaceWatcher, clear any cached workspace dotenv
state before (re)creating the chokidar watcher to avoid stale vars after a
deleted .env; call clearWorkspaceDotEnvVars(workspacePath) (or the same
clearEnvVars used in handlerOptions) right after closing an existing watcher and
before initiating chokidar.watch so the workspace dotenv cache is reset prior to
re-watching.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In
`@packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js`:
- Around line 86-101: The effect that attaches the click-outside handler
references handleCancelDotEnvCreate but omits it from the dependency array,
risking stale closures; fix by either adding handleCancelDotEnvCreate to the
effect dependencies ([isCreatingDotEnvInline, handleCancelDotEnvCreate]) or by
stabilizing it with useCallback (wrap handleCancelDotEnvCreate in useCallback so
its identity is stable) and then include it in the dependency array alongside
isCreatingDotEnvInline; ensure this change touches the useEffect that uses
isCreatingDotEnvInline, dotEnvCreateContainerRef, and handleCancelDotEnvCreate.
In
`@packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/index.js`:
- Around line 86-99: The useEffect that installs the click-outside handler
references handleCancelDotEnvCreate but doesn't list it in the dependency array;
update the code so the effect depends on a stable callback — either add
handleCancelDotEnvCreate to the dependency array of the useEffect or wrap
handleCancelDotEnvCreate in useCallback (so its identity is stable) and then
include that stable callback in the dependency list; keep references to
dotEnvCreateContainerRef and isCreatingDotEnvInline as they are when adjusting
dependencies.
🧹 Nitpick comments (2)
packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/StyledWrapper.js (1)
1-346: Consider extracting shared styles to reduce duplication.This StyledWrapper is nearly identical to
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js. Both define the same selectors (.sections-container,.environments-list,.btn-action,.environment-item,.empty-state, etc.) with identical styles.Consider extracting common styles into a shared module to reduce maintenance burden and ensure consistency.
packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/index.js (1)
1-784: Significant duplication with EnvironmentSettings/EnvironmentList.This component is ~90% identical to
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js. The main differences are:
- Workspace-scoped vs collection-scoped Redux actions
workspaceprop vscollectionprop for dotenv operations- Global environment actions vs collection environment actions
Consider extracting shared logic into a custom hook (e.g.,
useDotEnvManagement) or a higher-order component to reduce duplication. As per coding guidelines: "Prefer custom hooks for business logic, data fetching, and side-effects."
packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js
Outdated
Show resolved
Hide resolved
packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/index.js
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js`:
- Around line 75-87: The useEffect that checks dotEnvFiles and
selectedDotEnvFile is missing selectedDotEnvFile from its dependency array,
which risks stale reads; update the effect dependencies to include
selectedDotEnvFile so the effect reruns when selection changes (i.e., the effect
that contains checks using dotEnvFiles.some((f) => f.filename ===
selectedDotEnvFile) should depend on both dotEnvFiles and selectedDotEnvFile),
leaving the existing state setters (setSelectedDotEnvFile, setActiveView,
setIsDotEnvModified) unchanged.
| useEffect(() => { | ||
| if (dotEnvFiles.length === 0) { | ||
| setSelectedDotEnvFile(null); | ||
| setActiveView('environment'); | ||
| setIsDotEnvModified(false); | ||
| return; | ||
| } | ||
|
|
||
| const fileExists = dotEnvFiles.some((f) => f.filename === selectedDotEnvFile); | ||
| if (!selectedDotEnvFile || !fileExists) { | ||
| setSelectedDotEnvFile(dotEnvFiles[0].filename); | ||
| } | ||
| }, [dotEnvFiles]); |
There was a problem hiding this comment.
Missing selectedDotEnvFile in useEffect dependency array.
The effect references selectedDotEnvFile on line 83-84 but omits it from the dependency array at line 87. This can lead to stale closure issues when checking if the currently selected file still exists.
Suggested fix
- }, [dotEnvFiles]);
+ }, [dotEnvFiles, selectedDotEnvFile]);🤖 Prompt for AI Agents
In
`@packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js`
around lines 75 - 87, The useEffect that checks dotEnvFiles and
selectedDotEnvFile is missing selectedDotEnvFile from its dependency array,
which risks stale reads; update the effect dependencies to include
selectedDotEnvFile so the effect reruns when selection changes (i.e., the effect
that contains checks using dotEnvFiles.some((f) => f.filename ===
selectedDotEnvFile) should depend on both dotEnvFiles and selectedDotEnvFile),
leaving the existing state setters (setSelectedDotEnvFile, setActiveView,
setIsDotEnvModified) unchanged.
Description
JIRA
Contribution Checklist:
Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.
Publishing to New Package Managers
Please see here for more information.
Summary by CodeRabbit
New Features
Bug Fixes / UX