Skip to content

Conversation

@Shironex
Copy link
Collaborator

@Shironex Shironex commented Jan 25, 2026

Summary

  • Refactored the monolithic main.ts (~1000 lines) into focused, single-responsibility modules
  • Created a new electron/ directory structure with clear separation of concerns
  • Introduced shared IPC channel constants for consistency between main and preload scripts

Changes

New Module Structure

apps/ui/src/electron/
├── constants.ts              # Window sizing, port defaults, filenames
├── state.ts                  # Shared state container
├── index.ts                  # Re-exports
├── utils/
│   ├── port-manager.ts       # Port availability utilities
│   └── icon-manager.ts       # Icon path resolution
├── security/
│   └── api-key-manager.ts    # API key generation/storage
├── windows/
│   ├── window-bounds.ts      # Bounds load/save/validate
│   └── main-window.ts        # Window creation/lifecycle
├── server/
│   ├── static-server.ts      # Static file server
│   └── backend-server.ts     # Backend server management
└── ipc/
    ├── channels.ts           # IPC channel constants (single source of truth)
    ├── index.ts              # Handler aggregator
    ├── dialog-handlers.ts    # Dialog handlers
    ├── shell-handlers.ts     # Shell handlers
    ├── app-handlers.ts       # App handlers
    ├── auth-handlers.ts      # Auth handlers
    ├── window-handlers.ts    # Window handlers
    └── server-handlers.ts    # Server handlers

Benefits

  • Improved testability: Isolated modules can be unit tested independently
  • Better discoverability: Clear file structure makes it easy to find specific functionality
  • Maintainability: Each module has a single responsibility
  • Consistency: Shared IPC channel constants prevent string mismatches between main and preload

Test plan

  • npm run build:electron succeeds
  • npm run dev:electron launches correctly
  • File dialogs work (open directory, open file, save file)
  • External links open in browser
  • Window bounds persist across restarts
  • Backend server starts and health check passes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor

    • Restructured app internals for cleaner startup, shutdown, window and server management.
  • New Features

    • Persist and restore window size/position with on-screen validation.
    • External links open in the system browser; option to open files in an external editor.
    • Improved file/directory dialogs with validation and user-facing error messages.
  • Bug Fixes

    • Enforced minimum window size to avoid layout breakage and made server startup/shutdown more reliable.

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

… components

Extract the monolithic main.ts (~1000 lines) into focused modules:

- electron/constants.ts - Window sizing, port defaults, filenames
- electron/state.ts - Shared state container
- electron/utils/ - Port availability and icon utilities
- electron/security/ - API key management
- electron/windows/ - Window bounds and main window creation
- electron/server/ - Backend and static server management
- electron/ipc/ - IPC handlers with shared channel constants

Benefits:
- Improved testability with isolated modules
- Better discoverability and maintainability
- Single source of truth for IPC channels (used by both main and preload)
- Clear separation of concerns

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @Shironex, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant architectural improvement to the Electron main process by breaking down a large, single file into a well-structured set of modular components. This refactoring aims to enhance the application's long-term maintainability, make the codebase easier to navigate, and facilitate independent testing of different functionalities. The changes streamline the Electron lifecycle management and inter-process communication by centralizing configurations and handlers.

Highlights

  • Modularization of Electron Main Process: The monolithic 'main.ts' file has been refactored into smaller, single-responsibility modules, significantly improving code organization and maintainability.
  • New Directory Structure: A new 'electron/' directory has been introduced with a clear separation of concerns, categorizing modules by functionality such as IPC, security, windows, and server management.
  • Centralized IPC Channel Constants: Shared IPC channel constants are now used, ensuring consistency between the main and preload scripts and preventing string mismatches.
  • Improved Testability and Maintainability: The modular design allows for independent unit testing of components and enhances overall code maintainability and discoverability.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link

coderabbitai bot commented Jan 25, 2026

Warning

Rate limit exceeded

@Shironex has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 14 minutes and 35 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Extracts and modularizes Electron main-process logic into new files: centralized constants and state, IPC channel registry and handlers, port/icon utilities, API-key management, window bounds persistence, static/backend servers, and a barrel index; main.ts and preload now orchestrate and consume these modules.

Changes

Cohort / File(s) Summary
Core config & barrel
apps/ui/src/electron/constants.ts, apps/ui/src/electron/state.ts, apps/ui/src/electron/index.ts
New constants (sizes, default ports, filenames), ElectronState + singleton state, and a barrel export aggregating main-process modules.
IPC registry & aggregator
apps/ui/src/electron/ipc/channels.ts, apps/ui/src/electron/ipc/index.ts
Centralized IPC_CHANNELS const and registerAllHandlers() aggregator.
IPC handlers
apps/ui/src/electron/ipc/*.ts (app-handlers.ts, auth-handlers.ts, dialog-handlers.ts, server-handlers.ts, shell-handlers.ts, window-handlers.ts)
Modular IPC handlers for app metadata, auth/api-key access, dialogs (with path validation), server/ping, shell ops (openExternal/openPath/openInEditor), and window min-width updates.
Security (API key)
apps/ui/src/electron/security/api-key-manager.ts
ensureApiKey/getApiKey: load-or-generate API key, persist with secure permissions, update in-memory state.
Port & icon utilities
apps/ui/src/electron/utils/port-manager.ts, apps/ui/src/electron/utils/icon-manager.ts
Port availability checks and search; platform-aware icon path resolution with dev/prod fallbacks.
Servers
apps/ui/src/electron/server/backend-server.ts, apps/ui/src/electron/server/static-server.ts
start/stop backend (spawn, env, health polling, cross-platform kill) and start/stop static file server with SPA routing and MIME handling.
Window management
apps/ui/src/electron/windows/main-window.ts, apps/ui/src/electron/windows/window-bounds.ts
Main window creation (preload, icon, URL selection, devtools), load/validate/save/debounce window bounds with multi-display checks.
Entrypoints & preload
apps/ui/src/main.ts, apps/ui/src/preload.ts
main.ts refactored to orchestrate new modules (server startup, port discovery, API key, IPC registration, window creation); preload switched to IPC_CHANNELS constants for all ipcRenderer invocations.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Main as Main (electron)
  participant Static as StaticServer
  participant Backend as BackendProcess
  participant Renderer as Renderer (web)

  Main->>Main: findAvailablePort(DEFAULT_STATIC_PORT)
  Main->>Static: startStaticServer(port)
  Note right of Static: Creates HTTP server serving dist/public
  Main->>Main: findAvailablePort(DEFAULT_SERVER_PORT)
  Main->>Backend: startServer(port, env)
  Note right of Backend: spawn Node process with server entry
  Main->>Backend: waitForServer() (poll /api/health)
  Backend-->>Main: HTTP 200 (ready)
  Main->>Renderer: createWindow() (loads UI)
  Renderer->>Main: invoke IPC_CHANNELS.SERVER.GET_URL
  Main-->>Renderer: return http://localhost:<serverPort>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Refactor, Testers-Requested

Poem

🐰 Hopped through code to split and mend,

Constants, handlers — each a friend.
Ports found, servers spun with cheer,
IPC lined up, windows reappear.
A little rabbit says: "Modules, clear!" 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main objective of the changeset: refactoring a monolithic Electron main process module into modular, single-responsibility components distributed across multiple files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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


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

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This is an excellent and significant refactoring of the Electron main process. Breaking down the monolithic main.ts into single-responsibility modules under the new electron/ directory greatly improves the codebase's structure, maintainability, and testability. The new file organization is clear and logical. The introduction of a shared state object and centralized IPC channel constants are great patterns that enhance consistency and reduce coupling. I've found a couple of minor opportunities for simplification in the server startup logic, but overall this is a very solid and well-executed pull request.

Shironex and others added 2 commits January 25, 2026 20:49
Vite bundles all electron modules into a single main.js file,
so __dirname remains apps/ui/dist-electron regardless of source
file location. Updated path comments to clarify this behavior.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Address review feedback:
- Simplify tsx CLI path lookup by extracting path variables and
  reducing nested try-catch blocks
- Remove redundant try-catch around electronAppExists check in
  production server path validation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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 `@apps/ui/src/electron/constants.ts`:
- Around line 25-26: DEFAULT_SERVER_PORT and DEFAULT_STATIC_PORT use parseInt on
env vars and can become NaN for non-numeric values; update the module so after
parsing (parseInt or Number.parseInt) you check for NaN (Number.isNaN(parsed) or
!Number.isFinite(parsed)) and if invalid fall back to the numeric defaults 3008
and 3007 respectively; ensure the exported constants are always valid integers
so downstream code like findAvailablePort and server.listen never receive NaN.

In `@apps/ui/src/electron/ipc/auth-handlers.ts`:
- Around line 15-22: The GET_API_KEY handler returns the API key to any caller;
change the ipcMain.handle(IPC_CHANNELS.AUTH.GET_API_KEY, ...) to accept the IPC
event argument, validate the sender (e.g., check event.sender or
event.senderFrame.getURL()/event.sender.getURL() against an allowlist or compare
to the main window's webContents id) before returning sensitive data, and if the
sender is untrusted return null (or throw) as you already do when
state.isExternalServerMode; update references to IPC_CHANNELS.AUTH.GET_API_KEY,
state.isExternalServerMode, and state.apiKey accordingly.

In `@apps/ui/src/electron/ipc/dialog-handlers.ts`:
- Around line 44-53: The OPEN_FILE IPC handler currently spreads
renderer-provided options after a default properties array, allowing override of
the file-only intent; update the ipcMain.handle for
IPC_CHANNELS.DIALOG.OPEN_FILE so it merges options safely by
whitelisting/merging only allowed fields (e.g., accept extra properties but
replace or filter the properties array to ensure ['openFile'] is always
present), and validate returned paths similar to the OPEN_DIRECTORY handler (use
state.mainWindow and dialog.showOpenDialog result validation) to reject non-file
selections before returning the result to the renderer.

In `@apps/ui/src/electron/ipc/shell-handlers.ts`:
- Around line 43-47: The generated VS Code URL is missing the required slash
after "vscode://file" for Windows paths; update the construction logic around
normalizedPath and encodedPath so the final url always includes a slash
separator (e.g., ensure encodedPath begins with '/' before concatenating into
url), preserving the existing encoding logic (use normalizedPath, encodedPath)
and set url = `vscode://file/${encodedPathWithLeadingSlash}` so both Unix and
Windows paths produce `vscode://file/...`.
🧹 Nitpick comments (8)
apps/ui/src/electron/utils/icon-manager.ts (1)

19-33: Use app root instead of __dirname for icon path resolution

__dirname can shift between dev and packaged builds, which makes ../../dist/public fragile. Prefer anchoring to app.getAppPath() so the path is tied to the app root and less sensitive to bundling layout changes. Please confirm the packaged layout still matches dist/public.

♻️ Proposed refactor
-  const iconPath = isDev
-    ? path.join(__dirname, '../../public', iconFile)
-    : path.join(__dirname, '../../dist/public', iconFile);
+  const appPath = app.getAppPath();
+  const iconPath = isDev
+    ? path.join(appPath, 'public', iconFile)
+    : path.join(appPath, 'dist', 'public', iconFile);
apps/ui/src/electron/ipc/window-handlers.ts (1)

15-23: Consider removing the unused sidebarExpanded parameter.

The _sidebarExpanded parameter is accepted but not used since the logic now uses a fixed minimum width. If this API contract is no longer needed, removing the parameter would simplify both the handler and the preload API.

♻️ Optional: Remove unused parameter

If backward compatibility isn't required:

-  ipcMain.handle(IPC_CHANNELS.WINDOW.UPDATE_MIN_WIDTH, (_, _sidebarExpanded: boolean) => {
+  ipcMain.handle(IPC_CHANNELS.WINDOW.UPDATE_MIN_WIDTH, () => {

And update preload.ts:

-  updateMinWidth: (sidebarExpanded: boolean): Promise<void> =>
-    ipcRenderer.invoke(IPC_CHANNELS.WINDOW.UPDATE_MIN_WIDTH, sidebarExpanded),
+  updateMinWidth: (): Promise<void> =>
+    ipcRenderer.invoke(IPC_CHANNELS.WINDOW.UPDATE_MIN_WIDTH),
apps/ui/src/electron/ipc/shell-handlers.ts (1)

14-22: Consider validating URL scheme before opening external links.

shell.openExternal() can execute arbitrary protocol handlers, which poses a security risk if the renderer process is compromised. Consider restricting to safe schemes.

🔒 Suggested validation
   ipcMain.handle(IPC_CHANNELS.SHELL.OPEN_EXTERNAL, async (_, url: string) => {
     try {
+      const parsedUrl = new URL(url);
+      const allowedSchemes = ['http:', 'https:', 'mailto:'];
+      if (!allowedSchemes.includes(parsedUrl.protocol)) {
+        return { success: false, error: `Blocked URL scheme: ${parsedUrl.protocol}` };
+      }
       await shell.openExternal(url);
       return { success: true };
apps/ui/src/electron/windows/main-window.ts (1)

63-71: Simplify redundant URL loading branches.

The isDev fallback (lines 66-68) and production branch (lines 69-71) load the same URL. This can be consolidated.

♻️ Suggested simplification
   // Load Vite dev server in development or static server in production
   if (VITE_DEV_SERVER_URL) {
     state.mainWindow.loadURL(VITE_DEV_SERVER_URL);
-  } else if (isDev) {
-    // Fallback for dev without Vite server URL
-    state.mainWindow.loadURL(`http://localhost:${state.staticPort}`);
   } else {
+    // Static server URL (dev fallback or production)
     state.mainWindow.loadURL(`http://localhost:${state.staticPort}`);
   }
apps/ui/src/electron/windows/window-bounds.ts (1)

110-129: Apply minimum size even when recentering off-screen.

The off-screen branch returns width/height without MIN_* enforcement; clamping here keeps behavior consistent and guards against corrupted bounds.

♻️ Suggested refinement
-    return {
-      x: x + Math.floor((width - bounds.width) / 2),
-      y: y + Math.floor((height - bounds.height) / 2),
-      width: Math.min(bounds.width, width),
-      height: Math.min(bounds.height, height),
-      isMaximized: bounds.isMaximized,
-    };
+    const clampedWidth = Math.max(Math.min(bounds.width, width), MIN_WIDTH_COLLAPSED);
+    const clampedHeight = Math.max(Math.min(bounds.height, height), MIN_HEIGHT);
+    return {
+      x: x + Math.floor((width - clampedWidth) / 2),
+      y: y + Math.floor((height - clampedHeight) / 2),
+      width: clampedWidth,
+      height: clampedHeight,
+      isMaximized: Boolean(bounds.isMaximized),
+    };
apps/ui/src/electron/server/backend-server.ts (3)

66-88: Simplify redundant tsx resolution fallback logic.

The nested try-catch structure is confusing and contains duplicate fallback code (lines 72-78 and 81-87 are identical). The outer catch at line 80 will catch any exception from the inner block, making the inner catch at line 76 redundant for recovery purposes.

♻️ Suggested simplification
-    let tsxCliPath: string;
-    // Check for tsx in app bundle paths
-    try {
-      if (electronAppExists(path.join(serverNodeModules, 'dist/cli.mjs'))) {
-        tsxCliPath = path.join(serverNodeModules, 'dist/cli.mjs');
-      } else if (electronAppExists(path.join(rootNodeModules, 'dist/cli.mjs'))) {
-        tsxCliPath = path.join(rootNodeModules, 'dist/cli.mjs');
-      } else {
-        try {
-          tsxCliPath = require.resolve('tsx/cli.mjs', {
-            paths: [path.join(__dirname, '../../../server')],
-          });
-        } catch {
-          throw new Error("Could not find tsx. Please run 'npm install' in the server directory.");
-        }
-      }
-    } catch {
-      try {
-        tsxCliPath = require.resolve('tsx/cli.mjs', {
-          paths: [path.join(__dirname, '../../../server')],
-        });
-      } catch {
-        throw new Error("Could not find tsx. Please run 'npm install' in the server directory.");
-      }
-    }
+    let tsxCliPath: string | undefined;
+    // Check for tsx in app bundle paths
+    const serverTsxPath = path.join(serverNodeModules, 'dist/cli.mjs');
+    const rootTsxPath = path.join(rootNodeModules, 'dist/cli.mjs');
+    
+    try {
+      if (electronAppExists(serverTsxPath)) {
+        tsxCliPath = serverTsxPath;
+      } else if (electronAppExists(rootTsxPath)) {
+        tsxCliPath = rootTsxPath;
+      }
+    } catch {
+      // electronAppExists failed, fall through to require.resolve
+    }
+    
+    if (!tsxCliPath) {
+      try {
+        tsxCliPath = require.resolve('tsx/cli.mjs', {
+          paths: [path.join(__dirname, '../../../server')],
+        });
+      } catch {
+        throw new Error("Could not find tsx. Please run 'npm install' in the server directory.");
+      }
+    }

95-101: Preserve original error context when server path validation fails.

The catch block swallows any exception from electronAppExists and throws a generic message, losing the original error context (e.g., permission denied). This is inconsistent with the error handling pattern at lines 44-48.

♻️ Suggested fix
     try {
       if (!electronAppExists(serverPath)) {
         throw new Error(`Server not found at: ${serverPath}`);
       }
-    } catch {
-      throw new Error(`Server not found at: ${serverPath}`);
+    } catch (error) {
+      const originalError = error instanceof Error ? error.message : String(error);
+      throw new Error(`Server not found at: ${serverPath}. Reason: ${originalError}`);
     }

139-139: Non-null assertion on state.apiKey may cause silent failures.

Using state.apiKey! assumes ensureApiKey() was called successfully before startServer(). If apiKey is null/undefined, the server would receive undefined as the AUTOMAKER_API_KEY environment variable, potentially causing authentication issues that are hard to debug.

Consider adding a guard or fallback:

♻️ Suggested fix
     // Pass API key to server for CSRF protection
-    AUTOMAKER_API_KEY: state.apiKey!,
+    AUTOMAKER_API_KEY: state.apiKey ?? '',

Or add an explicit check earlier:

if (!state.apiKey) {
  throw new Error('API key not initialized. Call ensureApiKey() before starting server.');
}

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: 1

🤖 Fix all issues with AI agents
In `@apps/ui/src/electron/windows/main-window.ts`:
- Line 49: The window option titleBarStyle is macOS-only; update the
BrowserWindow creation in main-window.ts to set titleBarStyle only when
process.platform === 'darwin' (e.g., use a conditional to assign 'hiddenInset'
for darwin and a cross-platform default otherwise), ensuring the option is not
unconditionally applied so Windows/Linux get consistent styling; modify the code
path that constructs the BrowserWindow options (where titleBarStyle is currently
set) to use this platform check or a cross-platform alternative.
♻️ Duplicate comments (1)
apps/ui/src/electron/server/backend-server.ts (1)

70-90: Redundant fallback logic in tsx path resolution.

The outer catch block (lines 81-90) duplicates the require.resolve fallback already present in lines 77-79. If electronAppExists throws (which is unlikely given it's a simple existence check), both paths attempt the same resolution.

♻️ Simplified flow
     let tsxCliPath: string;
-    // Check for tsx in app bundle paths, fallback to require.resolve
     const serverTsxPath = path.join(serverNodeModules, 'dist/cli.mjs');
     const rootTsxPath = path.join(rootNodeModules, 'dist/cli.mjs');

     try {
       if (electronAppExists(serverTsxPath)) {
         tsxCliPath = serverTsxPath;
       } else if (electronAppExists(rootTsxPath)) {
         tsxCliPath = rootTsxPath;
       } else {
-        // Fallback to require.resolve
         tsxCliPath = require.resolve('tsx/cli.mjs', {
           paths: [path.join(__dirname, '../../server')],
         });
       }
-    } catch {
-      // electronAppExists threw or require.resolve failed
-      try {
-        tsxCliPath = require.resolve('tsx/cli.mjs', {
-          paths: [path.join(__dirname, '../../server')],
-        });
-      } catch {
+    } catch {
         throw new Error("Could not find tsx. Please run 'npm install' in the server directory.");
-      }
     }
🧹 Nitpick comments (6)
apps/ui/src/electron/server/static-server.ts (3)

62-80: Consider handling the case where index.html itself fails to load.

If the initial file isn't found (line 64 falls back to index.html), but then reading index.html also fails, the server returns a generic 500 error. This is acceptable, but logging the error would aid debugging in production.

Additionally, the nested callback pattern works but could be simplified with promisified versions if electronAppStat/electronAppReadFile support them.


83-89: Error listener is added but never removed.

The 'error' listener on line 88 is added but not cleaned up if an error occurs. While this doesn't cause issues during normal operation, if startStaticServer is called again after a failed attempt, the old listener remains. Consider removing the listener after resolution or rejection.

♻️ Suggested improvement
   return new Promise((resolve, reject) => {
+    const onError = (err: Error) => reject(err);
     state.staticServer!.listen(state.staticPort, () => {
+      state.staticServer!.removeListener('error', onError);
       logger.info('Static server running at http://localhost:' + state.staticPort);
       resolve();
     });
-    state.staticServer!.on('error', reject);
+    state.staticServer!.on('error', onError);
   });

95-101: stopStaticServer does not wait for close to complete.

server.close() is asynchronous. If you need to ensure the server is fully stopped before proceeding (e.g., during app quit or restart), consider returning a Promise or accepting a callback.

♻️ Optional async version
-export function stopStaticServer(): void {
+export async function stopStaticServer(): Promise<void> {
   if (state.staticServer) {
     logger.info('Stopping static server...');
-    state.staticServer.close();
-    state.staticServer = null;
+    await new Promise<void>((resolve) => {
+      state.staticServer!.close(() => resolve());
+    });
+    state.staticServer = null;
   }
 }
apps/ui/src/electron/server/backend-server.ts (1)

167-175: Process event handlers don't prevent multiple firings.

Both 'close' and 'error' events set state.serverProcess = null. If both events fire (which can happen), the second handler will execute on a null reference. While currently benign, this could mask issues.

♻️ Guard against multiple firings
   state.serverProcess.on('close', (code) => {
+    if (!state.serverProcess) return;
     serverLogger.info('Process exited with code', code);
     state.serverProcess = null;
   });

   state.serverProcess.on('error', (err) => {
+    if (!state.serverProcess) return;
     serverLogger.error('Failed to start server process:', err);
     state.serverProcess = null;
   });
apps/ui/src/electron/utils/icon-manager.ts (1)

22-28: Simplify platform branching.

The darwin and else branches are identical. Consider consolidating them for clarity.

♻️ Suggested simplification
   let iconFile: string;
   if (process.platform === 'win32') {
     iconFile = 'icon.ico';
-  } else if (process.platform === 'darwin') {
-    iconFile = 'logo_larger.png';
   } else {
+    // macOS, Linux, and others use PNG
     iconFile = 'logo_larger.png';
   }
apps/ui/src/electron/windows/main-window.ts (1)

64-72: Redundant conditional branches.

Lines 67-71 perform the same action for both isDev without VITE_DEV_SERVER_URL and production. This can be simplified.

♻️ Suggested simplification
   // Load Vite dev server in development or static server in production
   if (VITE_DEV_SERVER_URL) {
     state.mainWindow.loadURL(VITE_DEV_SERVER_URL);
-  } else if (isDev) {
-    // Fallback for dev without Vite server URL
-    state.mainWindow.loadURL(`http://localhost:${state.staticPort}`);
   } else {
+    // Fallback for dev without Vite server URL, or production
     state.mainWindow.loadURL(`http://localhost:${state.staticPort}`);
   }

Shironex and others added 2 commits January 25, 2026 21:02
- Guard against NaN ports from non-numeric env variables in constants.ts
- Validate IPC sender before returning API key to prevent leaking to
  untrusted senders (webviews, additional windows)
- Filter dialog properties to maintain file-only intent and prevent
  renderer from requesting directories via OPEN_FILE
- Fix Windows VS Code URL paths by ensuring leading slash after 'file'

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
titleBarStyle: 'hiddenInset' is a macOS-only option. Use conditional
spread to only apply it when process.platform === 'darwin', ensuring
Windows and Linux get consistent default styling.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@Shironex Shironex merged commit f3ce5ce into v0.14.0rc Jan 25, 2026
6 checks passed
@Shironex Shironex deleted the refactor/electron-main-process branch January 25, 2026 20:10
@Shironex Shironex self-assigned this Jan 25, 2026
@Shironex Shironex added Refactor A complete logic rewrite is requested or being performed for an issue. cleanup remove unused files in the codebase labels Jan 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cleanup remove unused files in the codebase Refactor A complete logic rewrite is requested or being performed for an issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants