Skip to content

Conversation

@RuntimeTerror10
Copy link
Member

@RuntimeTerror10 RuntimeTerror10 commented Jan 22, 2026

Changes included:

  • Default workspace creation
  • Opening existing local workspaces
  • Handling workspace creation edge cases
  • Preview path in finder

Summary by CodeRabbit

  • New Features
    • Added ability to open files and folders directly in the system file explorer
    • Introduced default workspace creation functionality
    • Added support for discovering and opening existing local workspaces with automatic configuration
    • Enhanced workspace management with improved path validation to prevent conflicts

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

@linear
Copy link

linear bot commented Jan 22, 2026

@coderabbitai
Copy link

coderabbitai bot commented Jan 22, 2026

Walkthrough

This PR introduces local workspace management functionality to the Electron app. Changes include: a new IPC handler to open file explorer paths in the main process; two configuration constants for workspace naming; two new IPC methods exposed for creating and opening local workspaces; multiple helper functions in fs-utils.ts for workspace path detection, validation, and configuration management; and new error codes and metadata support in type definitions. The workspace creation and opening logic includes path validation, ancestor directory traversal, and conflict detection.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • iostreamer-X
  • nafees87n
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[ENGG-5139]: Onboarding revamp changes' accurately reflects the main objective of the PR, which implements onboarding features including default workspace creation and opening existing local workspaces.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

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

🤖 Fix all issues with AI agents
In `@src/main/events.js`:
- Around line 303-306: The handler registered for "open-path-in-file-explorer"
does not await or validate shell.openPath, so supply a defensive fix in the
ipcMain.handle callback: validate that payload.resourcePath is a non-empty
string, then await shell.openPath(resourcePath); capture its returned string and
if it is non-empty treat that as an error (throw or return an error object) so
the renderer can handle it; otherwise return a success value. Update the handler
for "open-path-in-file-explorer" to perform these checks and return/throw the
appropriate result.

In `@src/renderer/actions/local-sync/fs-utils.ts`:
- Around line 506-521: The function getWorkspaceIdFromConfig currently throws
when parseFile returns an error, breaking the FileSystemResult flow; instead,
handle readResult.type === "error" by returning null (or a FileSystem-style
error wrapper if your surrounding code expects it) and avoid throwing—update the
error branch in getWorkspaceIdFromConfig (which uses createFsResource,
GLOBAL_CONFIG_FOLDER_PATH, parseFile, GlobalConfigRecordFileType) to return null
and optionally log the parse error so createDefaultWorkspace and
openExistingLocalWorkspace can continue when the global config is missing.
- Around line 646-662: The loop in getWorkspacePathFromSelectedPath can never
terminate on Windows because it only checks for "/" as the root; update the
termination logic to detect platform roots by checking when
path.dirname(currentPath) === currentPath or when currentPath equals
path.parse(currentPath).root (use Node's path.parse/currentPath root) so the
while loop exits for Windows roots like "C:\\"; keep using CONFIG_FILE,
appendPath and FsService.lstat as-is and break/return null when you hit the
filesystem root.
- Around line 233-249: The current case-insensitive string comparison using
getNormalizedPath between oldResource.path and newResource.path can miss real
conflicts on case-sensitive filesystems; change the rename/conflict check in the
block using alreadyExists and isSamePath to detect identity via filesystem
metadata: when both paths exist (alreadyExists true), call fs.stat (or lstat)
for oldResource.path and newResource.path and treat them as the same if their
device and inode (stat.dev and stat.ino) match; only fall back to the normalized
path comparison for platforms without inode semantics or when stat fails. Update
references around getIfFileExists/getIfFolderExists, isSamePath,
oldResource/newResource and keep the existing error return
(ErrorCode.EntityAlreadyExists/FileTypeEnum) for genuine conflicts.

Comment on lines +303 to +306
ipcMain.handle("open-path-in-file-explorer", async (event, payload = {}) => {
const { resourcePath } = payload;
shell.openPath(resourcePath);
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's check if the file exists and read the relevant section
if [ -f "src/main/events.js" ]; then
  wc -l src/main/events.js
  echo "---"
  sed -n '303,310p' src/main/events.js
fi

Repository: requestly/requestly-desktop-app

Length of output: 351


🏁 Script executed:

# Search for shell.openPath usage patterns in the codebase
rg "shell\.openPath" -B 3 -A 3

Repository: requestly/requestly-desktop-app

Length of output: 385


🏁 Script executed:

# Check for other IPC handlers to see error handling patterns
rg "ipcMain\.handle" -A 5 | head -100

Repository: requestly/requestly-desktop-app

Length of output: 5147


🌐 Web query:

shell.openPath electron return value async behavior

💡 Result:

Short answer: shell.openPath is asynchronous — it returns a Promise that resolves to a string: an empty string ("") on success or a non-empty error message on failure. It replaced the older synchronous/boolean shell.openItem API (Electron 9+) and should not block the event loop (there were platform/bug issues historically that were fixed in later releases). (freesoftwaredevlopment.github.io)

Citations:


Handle invalid paths and surface shell.openPath errors.

The shell.openPath call is not awaited, which risks unhandled rejections if resourcePath is missing or invalid. Since shell.openPath is asynchronous and returns either an empty string (success) or an error message (failure), awaiting it and returning the result to the renderer enables proper error handling.

🛠️ Suggested fix
 ipcMain.handle("open-path-in-file-explorer", async (event, payload = {}) => {
   const { resourcePath } = payload;
-  shell.openPath(resourcePath);
+  if (!resourcePath) {
+    return { success: false, error: "resourcePath is required" };
+  }
+  const error = await shell.openPath(resourcePath);
+  return error ? { success: false, error } : { success: true };
 });
📝 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
ipcMain.handle("open-path-in-file-explorer", async (event, payload = {}) => {
const { resourcePath } = payload;
shell.openPath(resourcePath);
});
ipcMain.handle("open-path-in-file-explorer", async (event, payload = {}) => {
const { resourcePath } = payload;
if (!resourcePath) {
return { success: false, error: "resourcePath is required" };
}
const error = await shell.openPath(resourcePath);
return error ? { success: false, error } : { success: true };
});
🤖 Prompt for AI Agents
In `@src/main/events.js` around lines 303 - 306, The handler registered for
"open-path-in-file-explorer" does not await or validate shell.openPath, so
supply a defensive fix in the ipcMain.handle callback: validate that
payload.resourcePath is a non-empty string, then await
shell.openPath(resourcePath); capture its returned string and if it is non-empty
treat that as an error (throw or return an error object) so the renderer can
handle it; otherwise return a success value. Update the handler for
"open-path-in-file-explorer" to perform these checks and return/throw the
appropriate result.

Comment on lines +233 to +249
if (newResource.type === "folder") {
return getIfFolderExists(newResource);
}
return getIfFileExists(newResource);
})();
const isSamePath = getNormalizedPath(oldResource.path).toLowerCase() === getNormalizedPath(newResource.path).toLowerCase();
const isSamePath =
getNormalizedPath(oldResource.path).toLowerCase() ===
getNormalizedPath(newResource.path).toLowerCase();
if (!isSamePath && alreadyExists) {
return {
type: 'error',
type: "error",
error: {
message: 'Entity already exists!',
message: "Entity already exists!",
fileType: FileTypeEnum.UNKNOWN,
path: newResource.path,
code: ErrorCode.EntityAlreadyExists,
}
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Case‑insensitive path compare can overwrite existing files on case‑sensitive FS.
If two paths differ only by case and both exist (possible on Linux), this bypasses the conflict check and allows a clobbering rename.

🛠️ Safer guard using inode/dev identity
-    const isSamePath =
-      getNormalizedPath(oldResource.path).toLowerCase() ===
-      getNormalizedPath(newResource.path).toLowerCase();
-    if (!isSamePath && alreadyExists) {
+    if (alreadyExists) {
+      const [oldStat, newStat] = await Promise.all([
+        FsService.lstat(oldResource.path).catch(() => null),
+        FsService.lstat(newResource.path).catch(() => null),
+      ]);
+      const sameInode =
+        oldStat &&
+        newStat &&
+        oldStat.ino === newStat.ino &&
+        oldStat.dev === newStat.dev;
+      if (!sameInode) {
         return {
           type: "error",
           error: {
             message: "Entity already exists!",
             fileType: FileTypeEnum.UNKNOWN,
             path: newResource.path,
             code: ErrorCode.EntityAlreadyExists,
           },
         };
+      }
     }
🤖 Prompt for AI Agents
In `@src/renderer/actions/local-sync/fs-utils.ts` around lines 233 - 249, The
current case-insensitive string comparison using getNormalizedPath between
oldResource.path and newResource.path can miss real conflicts on case-sensitive
filesystems; change the rename/conflict check in the block using alreadyExists
and isSamePath to detect identity via filesystem metadata: when both paths exist
(alreadyExists true), call fs.stat (or lstat) for oldResource.path and
newResource.path and treat them as the same if their device and inode (stat.dev
and stat.ino) match; only fall back to the normalized path comparison for
platforms without inode semantics or when stat fails. Update references around
getIfFileExists/getIfFolderExists, isSamePath, oldResource/newResource and keep
the existing error return (ErrorCode.EntityAlreadyExists/FileTypeEnum) for
genuine conflicts.

Comment on lines +506 to +521
async function getWorkspaceIdFromConfig(
workspaceFolderPath: string | null
): Promise<string | null> {
const globalConfigFileResource = createFsResource({
rootPath: GLOBAL_CONFIG_FOLDER_PATH,
path: appendPath(GLOBAL_CONFIG_FOLDER_PATH, GLOBAL_CONFIG_FILE_NAME),
type: "file",
});
const readResult = await parseFile({
resource: globalConfigFileResource,
fileType: new GlobalConfigRecordFileType(),
});

if (readResult.type === "error") {
throw new Error("Failed to read global config file");
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid throwing in getWorkspaceIdFromConfig; it breaks the FileSystemResult flow.
This throws on read errors, which can make createDefaultWorkspace / openExistingLocalWorkspace fail when the global config is missing.

🛠️ Suggested fix
-  if (readResult.type === "error") {
-    throw new Error("Failed to read global config file");
-  }
+  if (readResult.type === "error") {
+    return null;
+  }
🤖 Prompt for AI Agents
In `@src/renderer/actions/local-sync/fs-utils.ts` around lines 506 - 521, The
function getWorkspaceIdFromConfig currently throws when parseFile returns an
error, breaking the FileSystemResult flow; instead, handle readResult.type ===
"error" by returning null (or a FileSystem-style error wrapper if your
surrounding code expects it) and avoid throwing—update the error branch in
getWorkspaceIdFromConfig (which uses createFsResource,
GLOBAL_CONFIG_FOLDER_PATH, parseFile, GlobalConfigRecordFileType) to return null
and optionally log the parse error so createDefaultWorkspace and
openExistingLocalWorkspace can continue when the global config is missing.

Comment on lines +646 to +662
async function getWorkspacePathFromSelectedPath(
selectedPath: string
): Promise<string | null> {
let currentPath = selectedPath;
while (currentPath !== "/") {
const workspacePathExists = await FsService.lstat(
appendPath(currentPath, CONFIG_FILE)
)
.then((stats) => stats.isFile())
.catch(() => false);
if (workspacePathExists) {
return currentPath;
}
currentPath = path.dirname(currentPath);
}
return null;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Potential infinite loop on Windows roots in getWorkspacePathFromSelectedPath.
The loop only exits at "/", so a Windows root like C:\ will never terminate.

🛠️ Suggested fix
-  let currentPath = selectedPath;
-  while (currentPath !== "/") {
+  let currentPath = selectedPath;
+  // eslint-disable-next-line no-constant-condition
+  while (true) {
     const workspacePathExists = await FsService.lstat(
       appendPath(currentPath, CONFIG_FILE)
     )
       .then((stats) => stats.isFile())
       .catch(() => false);
     if (workspacePathExists) {
       return currentPath;
     }
-    currentPath = path.dirname(currentPath);
+    const parentPath = path.dirname(currentPath);
+    if (parentPath === currentPath) {
+      break;
+    }
+    currentPath = parentPath;
   }
   return null;
📝 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
async function getWorkspacePathFromSelectedPath(
selectedPath: string
): Promise<string | null> {
let currentPath = selectedPath;
while (currentPath !== "/") {
const workspacePathExists = await FsService.lstat(
appendPath(currentPath, CONFIG_FILE)
)
.then((stats) => stats.isFile())
.catch(() => false);
if (workspacePathExists) {
return currentPath;
}
currentPath = path.dirname(currentPath);
}
return null;
}
async function getWorkspacePathFromSelectedPath(
selectedPath: string
): Promise<string | null> {
let currentPath = selectedPath;
// eslint-disable-next-line no-constant-condition
while (true) {
const workspacePathExists = await FsService.lstat(
appendPath(currentPath, CONFIG_FILE)
)
.then((stats) => stats.isFile())
.catch(() => false);
if (workspacePathExists) {
return currentPath;
}
const parentPath = path.dirname(currentPath);
if (parentPath === currentPath) {
break;
}
currentPath = parentPath;
}
return null;
}
🤖 Prompt for AI Agents
In `@src/renderer/actions/local-sync/fs-utils.ts` around lines 646 - 662, The loop
in getWorkspacePathFromSelectedPath can never terminate on Windows because it
only checks for "/" as the root; update the termination logic to detect platform
roots by checking when path.dirname(currentPath) === currentPath or when
currentPath equals path.parse(currentPath).root (use Node's
path.parse/currentPath root) so the while loop exits for Windows roots like
"C:\\"; keep using CONFIG_FILE, appendPath and FsService.lstat as-is and
break/return null when you hit the filesystem root.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants