Skip to content

workspace renaming with path update#7437

Merged
bijin-bruno merged 4 commits intousebruno:mainfrom
naman-bruno:bugfix/workspace-rename
Mar 11, 2026
Merged

workspace renaming with path update#7437
bijin-bruno merged 4 commits intousebruno:mainfrom
naman-bruno:bugfix/workspace-rename

Conversation

@naman-bruno
Copy link
Collaborator

@naman-bruno naman-bruno commented Mar 11, 2026

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

  • New Features

    • Workspace renaming can now move a workspace to a new filesystem location and will automatically activate the moved workspace when applicable.
    • Rename now safely handles conflicts and attempts rollback on failure to preserve workspace state.
  • Tests

    • Added tests covering rename/relocation, name sanitization, conflict handling (including case-insensitive filesystems), and config preservation.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 11, 2026

Walkthrough

Adds path-aware workspace renaming: renderer dispatches renderer:rename-workspace to main, main calls renameWorkspace which may move the folder and return a new path; IPC updates watchers/last-opened state; renderer-side Redux recreates workspace entries when path changes or updates name in-place otherwise.

Changes

Cohort / File(s) Summary
Redux Action Layer
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js
Rewrote renameWorkspaceAction to call renderer:rename-workspace directly, handle { newWorkspacePath } by generating a new UID, removing the old workspace, creating a new workspace with updated UID/name/path, and preserving activation state; falls back to in-place updateWorkspace when no path change.
Electron IPC Layer
packages/bruno-electron/src/ipc/workspace.js
Replaced updateWorkspaceName call with renameWorkspace in renderer:rename-workspace handler; when newWorkspacePath is returned, updates file watcher and refreshes last-opened-workspaces, returning the new path to renderer.
Workspace Configuration
packages/bruno-electron/src/utils/workspace-config.js
Added renameWorkspace(workspacePath, newName) which sanitizes folder names, checks conflicts, renames the workspace directory when needed, updates workspace.yml via updateWorkspaceName, attempts rollback on failure, and returns { newWorkspacePath } or { newWorkspacePath: null }.
Tests
packages/bruno-electron/tests/utils/workspace-config.spec.js
Added tests for renameWorkspace covering folder renames, YAML updates, name sanitization, conflict/error handling, case-insensitive FS behavior, and rollback/preservation scenarios.

Sequence Diagram

sequenceDiagram
    participant Redux as Redux Action (renderer)
    participant IPC as IPC (main)
    participant WsConfig as workspace-config (main util)
    participant FS as File System

    Redux->>IPC: renderer:rename-workspace(path, newName)
    IPC->>WsConfig: renameWorkspace(path, newName)
    WsConfig->>FS: check target folder exists / sanitize name
    alt conflict detected
        WsConfig-->>IPC: throw/error
    else target available
        WsConfig->>FS: rename folder (if name changes)
        WsConfig->>WsConfig: update workspace.yml (updateWorkspaceName)
        alt folder moved
            WsConfig-->>IPC: { newWorkspacePath }
        else no path change
            WsConfig-->>IPC: { newWorkspacePath: null }
        end
    end

    alt newWorkspacePath returned
        IPC->>IPC: update file watcher for new path
        IPC->>IPC: refresh lastOpenedWorkspaces entries
        IPC-->>Redux: { newWorkspacePath }
        Redux->>Redux: remove old workspace\ncreate new workspace (new UID/path/name)\nreactivate if old was active
    else in-place rename
        IPC-->>Redux: { success: true }
        Redux->>Redux: update workspace name in place
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • lohit-bruno
  • bijin-bruno
  • helloanoop

Poem

🌱 A folder sheds a name and finds a road,
IPC hums softly, moves the treasured load.
Redux crafts a UID, the watchers bend,
A path reborn — the workspace meets its end,
Same desk, new door, the workflow moves along.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding workspace renaming functionality with path update support, which aligns with the core modifications across all affected files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/bruno-electron/src/ipc/workspace.js (1)

271-291: Handler exposes partial failure risk.

The sequence of operations (rename → watcher update → lastOpenedWorkspaces update) isn't transactional. If the app crashes after renameWorkspace succeeds but before lastOpenedWorkspaces.add() completes, the persisted store will reference the old (now non-existent) path.

The existing renderer:get-last-opened-workspaces handler (lines 245-268) does prune invalid paths on startup, which mitigates this for the next session. This is acceptable for a desktop app but worth noting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-electron/src/ipc/workspace.js` around lines 271 - 291, After
renameWorkspace succeeds, persist the updated lastOpenedWorkspaces immediately
(call lastOpenedWorkspaces.remove(workspacePath) and
lastOpenedWorkspaces.add(result.newWorkspacePath)) before touching
workspaceWatcher so the stored paths are updated even if the app crashes; then
perform watcher updates inside a try/catch (wrap workspaceWatcher.removeWatcher
and workspaceWatcher.add) and log but do not rethrow watcher errors; only return
{ success: true, newWorkspacePath } after the store update succeeds.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js`:
- Around line 537-538: The client currently always uses
generateUidBasedOnHash(result.newWorkspacePath) to create newWorkspaceUid, which
diverges from Electron's getWorkspaceUid behavior; change the logic in the
workspace rename/create flow (where newWorkspaceUid is assigned in actions.js)
to mirror Electron: if result.newWorkspacePath equals the default workspace
path, call defaultWorkspaceManager.getDefaultWorkspaceUid() (or the equivalent
helper that returns the default UID) and use that UID; otherwise fall back to
generateUidBasedOnHash(result.newWorkspacePath). Ensure you reference and use
the same default workspace-path check used by getWorkspaceUid so both sides
produce identical UIDs.

In `@packages/bruno-electron/src/utils/workspace-config.js`:
- Around line 584-585: The rename of the workspace folder
(fs.renameSync(workspacePath, newWorkspacePath)) is not rolled back if
updateWorkspaceName(newWorkspacePath, newName) throws; wrap the YAML update in a
try/catch so that on failure you attempt to restore the original folder name
(fs.renameSync(newWorkspacePath, workspacePath)) and then rethrow or propagate
the error. Ensure you catch and handle any error from the rollback attempt too
(log or aggregate) so failures during rollback don't silence the original error;
reference fs.renameSync, workspacePath, newWorkspacePath, and
updateWorkspaceName to locate and implement the change.

In `@packages/bruno-electron/tests/utils/workspace-config.spec.js`:
- Around line 152-163: The test assumes a case-insensitive filesystem; detect
filesystem behavior at runtime and adjust the assertions accordingly: add a
small helper (inline in the spec) that uses fs to create a temp dir/file via
createWorkspace or fs.mkdtemp and checks whether a differently-cased path refers
to the same inode (or whether fs.existsSync with changed case returns true) to
determine case-sensitivity, then in the test for renameWorkspace use that helper
to either expect result.newWorkspacePath toBeNull and
getWorkspaceName(lowerPath) toBe('MyWorkspace') on case-insensitive systems, or
expect result.newWorkspacePath toEqual the actual renamed path (and verify the
folder rename) on case-sensitive systems; reference the existing
createWorkspace, renameWorkspace, and getWorkspaceName symbols to locate where
to update the test.

---

Nitpick comments:
In `@packages/bruno-electron/src/ipc/workspace.js`:
- Around line 271-291: After renameWorkspace succeeds, persist the updated
lastOpenedWorkspaces immediately (call
lastOpenedWorkspaces.remove(workspacePath) and
lastOpenedWorkspaces.add(result.newWorkspacePath)) before touching
workspaceWatcher so the stored paths are updated even if the app crashes; then
perform watcher updates inside a try/catch (wrap workspaceWatcher.removeWatcher
and workspaceWatcher.add) and log but do not rethrow watcher errors; only return
{ success: true, newWorkspacePath } after the store update succeeds.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b96db8a6-7545-42c9-8aa9-7493540b7277

📥 Commits

Reviewing files that changed from the base of the PR and between 9bdd439 and c6d8cf4.

📒 Files selected for processing (4)
  • packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js
  • packages/bruno-electron/src/ipc/workspace.js
  • packages/bruno-electron/src/utils/workspace-config.js
  • packages/bruno-electron/tests/utils/workspace-config.spec.js

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/bruno-electron/tests/utils/workspace-config.spec.js`:
- Around line 124-130: Update the test so it proves the config write actually
happened by seeding a workspace whose folder basename already equals the
sanitized target but whose workspace.yml info.name is different; call
renameWorkspace(workspacePath, 'Untitled Workspace'), assert
result.newWorkspacePath is null and the folder still exists, and then verify the
workspace.yml (via getWorkspaceName(workspacePath)) now contains 'Untitled
Workspace' (i.e. change the initial info.name to something else before calling
renameWorkspace so the assertion fails if the config-write step is skipped).
- Around line 176-202: The test should assert that renameWorkspace preserves all
fields that renameWorkspace rewrites in workspace.yml, not just info.name, one
collection and docs; update the spec in workspace-config.spec.js to load the
parsed config and add assertions for other top-level keys that could be
rewritten (e.g., opencollection, info.type, specs, activeEnvironmentUid if
present, full collections entries including path and any extra collection
fields, and any other metadata expected to remain unchanged) while still
checking that info.name equals 'My Project'—use the existing renameWorkspace
result and newConfigPath to locate the file and verify each field that
renameWorkspace is responsible for preserving.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3aca6511-027f-4533-a29d-9d5ccbe467fb

📥 Commits

Reviewing files that changed from the base of the PR and between c6d8cf4 and cd9cbbb.

📒 Files selected for processing (2)
  • packages/bruno-electron/src/utils/workspace-config.js
  • packages/bruno-electron/tests/utils/workspace-config.spec.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/bruno-electron/src/utils/workspace-config.js

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
packages/bruno-electron/tests/utils/workspace-config.spec.js (1)

156-167: ⚠️ Potential issue | 🟠 Major

Make this case-only rename test filesystem-aware.

This assertion is still OS-dependent: on case-sensitive filesystems, myworkspace and MyWorkspace are different paths, so renameWorkspace() can legitimately return a new path instead of null. Please branch the expectation based on detected filesystem case-sensitivity, or skip this scenario where that guarantee does not hold.

Suggested adjustment
   test('handles case-only rename by updating config without renaming folder', async () => {
     const lowerPath = createWorkspace('myworkspace', 'myworkspace');

     const result = await renameWorkspace(lowerPath, 'MyWorkspace');

-    expect(result.newWorkspacePath).toBeNull();
-    expect(fs.existsSync(lowerPath)).toBe(true);
-    expect(getWorkspaceName(lowerPath)).toBe('MyWorkspace');
+    const probe = path.join(parentDir, 'CaseProbe');
+    fs.mkdirSync(probe);
+    const isCaseInsensitive = fs.existsSync(path.join(parentDir, 'caseprobe'));
+    fs.rmSync(probe, { recursive: true, force: true });
+
+    if (isCaseInsensitive) {
+      expect(result.newWorkspacePath).toBeNull();
+      expect(fs.existsSync(lowerPath)).toBe(true);
+      expect(getWorkspaceName(lowerPath)).toBe('MyWorkspace');
+      return;
+    }
+
+    const renamedPath = path.join(parentDir, 'MyWorkspace');
+    expect(result.newWorkspacePath).toBe(renamedPath);
+    expect(fs.existsSync(renamedPath)).toBe(true);
+    expect(getWorkspaceName(renamedPath)).toBe('MyWorkspace');
   });

As per coding guidelines, "Ensure tests are deterministic and reproducible. No randomness, timing dependencies, or environment-specific assumptions without explicit control" and "**/*: Bruno is a cross-platform Electron desktop app that runs on macOS, Windows, and Linux. Ensure that all code is OS-agnostic".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-electron/tests/utils/workspace-config.spec.js` around lines
156 - 167, The test 'handles case-only rename by updating config without
renaming folder' assumes case-insensitive filesystems; make it filesystem-aware
by detecting whether the fs is case-sensitive (e.g., create a temp file/folder
with a mixed-case duplicate and check if paths are distinct) before asserting
expectations for renameWorkspace; if filesystem is case-insensitive assert
result.newWorkspacePath === null and that fs.existsSync(lowerPath) is true and
getWorkspaceName(lowerPath) === 'MyWorkspace', otherwise assert
result.newWorkspacePath equals the new path returned (not null) and that both
the old and new paths exist and getWorkspaceName(result.newWorkspacePath) ===
'MyWorkspace' (or skip the case-only branch when detection indicates
sensitivity). Use the existing helpers createWorkspace, renameWorkspace,
getWorkspaceName and fs.existsSync to implement this branching logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/bruno-electron/tests/utils/workspace-config.spec.js`:
- Around line 156-167: The test 'handles case-only rename by updating config
without renaming folder' assumes case-insensitive filesystems; make it
filesystem-aware by detecting whether the fs is case-sensitive (e.g., create a
temp file/folder with a mixed-case duplicate and check if paths are distinct)
before asserting expectations for renameWorkspace; if filesystem is
case-insensitive assert result.newWorkspacePath === null and that
fs.existsSync(lowerPath) is true and getWorkspaceName(lowerPath) ===
'MyWorkspace', otherwise assert result.newWorkspacePath equals the new path
returned (not null) and that both the old and new paths exist and
getWorkspaceName(result.newWorkspacePath) === 'MyWorkspace' (or skip the
case-only branch when detection indicates sensitivity). Use the existing helpers
createWorkspace, renameWorkspace, getWorkspaceName and fs.existsSync to
implement this branching logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ba54065c-8799-40f1-a779-7864ee31f2f8

📥 Commits

Reviewing files that changed from the base of the PR and between a0fbedb and 3af2b5f.

📒 Files selected for processing (1)
  • packages/bruno-electron/tests/utils/workspace-config.spec.js

@bijin-bruno bijin-bruno merged commit e7c2c7c into usebruno:main Mar 11, 2026
9 checks passed
naman-bruno added a commit that referenced this pull request Mar 12, 2026
bijin-bruno pushed a commit that referenced this pull request Mar 12, 2026
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