Skip to content

refactor(ipc): modularize IPC handlers into separate modules#26

Merged
newbe36524 merged 1 commit intomainfrom
feature/newgo
Feb 26, 2026
Merged

refactor(ipc): modularize IPC handlers into separate modules#26
newbe36524 merged 1 commit intomainfrom
feature/newgo

Conversation

@newbe36524
Copy link
Contributor

@newbe36524 newbe36524 commented Feb 26, 2026

Refactor monolithic IPC handler code into separate, focused modules for better maintainability and code organization.

  • Create dedicated handler modules for window, server, webService, version, dependency, packageSource, license, onboarding, dataDirectory, region, LLM, RSS, debug, and view operations
  • Add shared types for handler responses and error handling
  • Centralize handler exports in handlers/index.ts
  • Update preload API to support new handler structure
  • Integrate new handler system in main.ts

Summary by CodeRabbit

  • New Features
    • Added data directory management settings with path validation and disk usage metrics
    • Integrated directory picker for easy path selection
    • Ability to restore default data directory configuration

Refactor monolithic IPC handler code into separate, focused modules for better maintainability and code organization.

- Create dedicated handler modules for window, server, webService, version, dependency, packageSource, license, onboarding, dataDirectory, region, LLM, RSS, debug, and view operations
- Add shared types for handler responses and error handling
- Centralize handler exports in handlers/index.ts
- Update preload API to support new handler structure
- Integrate new handler system in main.ts

Co-Authored-By: Hagicode <noreply@hagicode.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

📝 Walkthrough

Walkthrough

This PR removes the entire UI/UX Pro Max skill system (including BM25 search, design system generation, and CLI scripts) and introduces comprehensive data directory management across the Electron application, spanning main process, IPC handlers, path validation, configuration persistence, and renderer UI.

Changes

Cohort / File(s) Summary
Removed: UI/UX Pro Max Skill
.claude/skills/ui-ux-pro-max/SKILL.md, .claude/skills/ui-ux-pro-max/scripts/core.py, .claude/skills/ui-ux-pro-max/scripts/design_system.py, .claude/skills/ui-ux-pro-max/scripts/search.py
Complete removal of UI/UX Pro Max design system skill, including documentation (377 lines), BM25-based search implementation (253 lines), design system generation logic (1067 lines), and CLI script (106 lines).
Configuration & Path Management
src/main/config.ts, src/main/config-manager.ts, src/main/path-manager.ts
Added new config fields for data/shutdown/recording/logs directories and language management; introduced path validation framework, YAML config reading, storage info calculation, and new ConfigManager method updateAllDataDirs.
IPC Handler Framework & Registration
src/main/ipc/handlers/types.ts, src/main/ipc/handlers/index.ts, src/main/ipc/handlers/windowHandlers.ts, src/main/ipc/handlers/serverHandlers.ts, src/main/ipc/handlers/webServiceHandlers.ts, src/main/ipc/handlers/versionHandlers.ts, src/main/ipc/handlers/dependencyHandlers.ts, src/main/ipc/handlers/packageSourceHandlers.ts, src/main/ipc/handlers/licenseHandlers.ts, src/main/ipc/handlers/onboardingHandlers.ts, src/main/ipc/handlers/dataDirectoryHandlers.ts, src/main/ipc/handlers/regionHandlers.ts, src/main/ipc/handlers/llmHandlers.ts, src/main/ipc/handlers/rssHandlers.ts, src/main/ipc/handlers/debugHandlers.ts, src/main/ipc/handlers/viewHandlers.ts
New comprehensive IPC handler architecture with 14 handler modules (14 new files, ~2500 lines), including data directory operations (open picker, get, set, validate, restore), dependency/version management, web service control, package sources, licensing, onboarding, and more; barrel index centralizes exports.
Main Process Integration
src/main/main.ts, src/main/web-service-manager.ts, src/main/llm-installation-manager.ts
Expanded main process with PathManager/ConfigManager initialization, data directory configuration loading from YAML, IPC surface expansion for data directory management, and DataDir synchronization to appsettings.yml; LLM manager enhanced with platform-specific Claude launch logic (Windows/macOS/Linux terminal detection).
Preload & Renderer API Exposure
src/preload/index.ts
Extended ElectronAPI with new data directory operations (get, set, validate, getStorageInfo, restoreDefault, openPicker), server/web-service lifecycle methods, dependency/manifest operations, package source management, validation/storage types, and numerous event listeners.
Renderer State Management & UI
src/renderer/store/slices/dataDirectorySlice.ts, src/renderer/store/index.ts, src/renderer/components/settings/DataDirectorySettings.tsx, src/renderer/components/SettingsPage.tsx
New Redux slice for data directory state (path, validation, storage info, loading/saving flags), thunks for fetch/validate/save/storage operations, new DataDirectorySettings component with debounced validation, storage display, and UX actions, plus integration into SettingsPage tabs.
Internationalization
src/renderer/i18n/locales/en-US/pages.json, src/renderer/i18n/locales/zh-CN/pages.json, src/renderer/i18n/index.ts
Added comprehensive data directory UI translations (English & Simplified Chinese), including path labels, validation messages, storage metrics, action button text, and notes; added initialization logging for i18n startup diagnostics.

Sequence Diagram(s)

sequenceDiagram
    participant Renderer as Renderer
    participant IPC as IPC Layer
    participant PathMgr as PathManager
    participant ConfigMgr as ConfigManager
    participant FS as File System

    Renderer->>IPC: data-directory:set(path)
    IPC->>PathMgr: validatePath(path)
    PathMgr->>FS: check absolute, writability
    PathMgr-->>IPC: ValidationResult
    IPC->>ConfigMgr: setDataDirectoryPath(path)
    ConfigMgr->>FS: persist to appsettings.yml
    FS-->>ConfigMgr: ✓
    IPC->>PathMgr: getStorageInfo(path)
    PathMgr->>FS: calculateDirectorySize()
    FS-->>PathMgr: StorageInfo
    PathMgr-->>IPC: StorageInfo
    IPC-->>Renderer: { success, error?, storageInfo }
    Renderer->>Renderer: Update Redux state
    Renderer->>Renderer: Render UI with validation + storage
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • add basic framework #1 — Modifies AppConfig and ConfigManager in the same config layer, directly related to config field additions.
  • new installation feature #23 — Overlaps on LlmInstallationManager, Claude config IPC handlers, and preload API surface expansion for LLM/preset features.
  • mvp version #3 — Touches the same web-service-manager, dependency management, and extensive IPC/preload surfaces as part of MVP infrastructure.

Poem

🐰 Hopping through data paths with glee,
Validation checks from A to Z,
Storage metrics now on display,
While old UI skills fade away,
The rabbit hops to greet the day! 🌱

🚥 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 accurately summarizes the main objective: refactoring IPC handlers into separate modules. It is specific, clear, and reflects the core change across the entire changeset.
Docstring Coverage ✅ Passed Docstring coverage is 91.18% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/newgo

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

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/llm-installation-manager.ts (1)

216-220: ⚠️ Potential issue | 🟡 Minor

Hardcoded Chinese error message should use i18n.

The error message '无法找到可用的终端模拟器' is hardcoded in Chinese. For consistency with the application's internationalization, this should be a translatable string or an error code that the renderer can translate.

🌐 Suggested fix
         return {
           success: false,
-          error: '无法找到可用的终端模拟器',
+          error: 'NO_TERMINAL_EMULATOR_FOUND',
         };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/llm-installation-manager.ts` around lines 216 - 220, Replace the
hardcoded Chinese error string in the returned object (the block returning {
success: false, error: '无法找到可用的终端模拟器' }) with a translatable value: either call
the app i18n helper (e.g., i18n.t('errors.no_terminal_emulator') or
t('errors.no_terminal_emulator')) or return a stable error code (e.g., {
success: false, errorCode: 'NO_TERMINAL_EMULATOR' }) so the renderer can
localize it; update llm-installation-manager.ts accordingly and ensure any new
error key is added to the translation files or handled by the renderer.
🟠 Major comments (17)
src/main/web-service-manager.ts-1441-1444 (1)

1441-1444: ⚠️ Potential issue | 🟠 Major

Clear DataDir when no directory is configured.

If getDataDirectory() returns empty/undefined, the previous DataDir stays in appsettings.yml, so the service can keep using stale path configuration.

Suggested fix
       const dataDir = this.pathManager.getDataDirectory();
       if (dataDir) {
         config.DataDir = dataDir;
         log.info('[WebService] Syncing data directory to config:', dataDir);
+      } else {
+        delete config.DataDir;
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/web-service-manager.ts` around lines 1441 - 1444, When
getDataDirectory() returns empty/undefined the previous config.DataDir value is
left unchanged causing stale paths to persist; update the logic around
getDataDirectory() (where config and DataDir are handled) so that when dataDir
is falsy you explicitly clear config.DataDir (set to undefined or empty string)
and log that the DataDir was cleared (use the same log object, e.g. log.info)
instead of only setting it when dataDir is present.
src/renderer/components/settings/DataDirectorySettings.tsx-38-43 (1)

38-43: 🛠️ Refactor suggestion | 🟠 Major

Remove debug logging before merging.

This useEffect logs i18n state on every render. Remove or guard behind a development flag before production:

🧹 Proposed fix
-  // Debug: Check i18n initialization
-  useEffect(() => {
-    console.log('[DataDirectorySettings] t function:', typeof t);
-    console.log('[DataDirectorySettings] i18n ready:', !!t);
-    console.log('[DataDirectorySettings] Sample translation:', t('settings.title'));
-  }, [t]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/settings/DataDirectorySettings.tsx` around lines 38 -
43, Remove the debug logging inside the useEffect in DataDirectorySettings:
delete the three console.log calls that reference t (the i18n function) or wrap
them in a development-only guard (e.g., if (process.env.NODE_ENV !==
'production') { ... }) so they do not run in production; ensure the useEffect
still lists [t] as its dependency and contains no leftover debug code.
src/main/ipc/handlers/windowHandlers.ts-68-117 (1)

68-117: ⚠️ Potential issue | 🟠 Major

Validate URL scheme before loading external content.

loadURL(url) accepts any string. A malicious or misconfigured call could load file:// or javascript: URLs. Validate the URL scheme:

🛡️ Proposed URL validation
   ipcMain.handle('open-hagicode-in-app', async (_, url: string) => {
     if (!url) {
       console.error('[WindowHandlers] No URL provided for open-hagicode-in-app');
       return false;
     }
+    
+    // Validate URL scheme
+    try {
+      const parsedUrl = new URL(url);
+      if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
+        console.error('[WindowHandlers] Invalid URL protocol:', parsedUrl.protocol);
+        return false;
+      }
+    } catch {
+      console.error('[WindowHandlers] Invalid URL format:', url);
+      return false;
+    }
+    
     try {
       console.log('[WindowHandlers] Opening Hagicode in app window:', url);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/ipc/handlers/windowHandlers.ts` around lines 68 - 117, In the
ipcMain.handle('open-hagicode-in-app') handler, validate the incoming url before
calling hagicodeWindow.loadURL: parse the url (using the URL constructor or a
safe parser) and ensure its protocol is only 'http:' or 'https:' (reject
'file:', 'javascript:', data:, etc.), log an error and return false for invalid
schemes; perform this check early (before creating the BrowserWindow) and only
call loadURL(url) when the scheme is allowed to prevent loading local or
executable content.
src/main/ipc/handlers/windowHandlers.ts-87-94 (1)

87-94: ⚠️ Potential issue | 🟠 Major

Add URL validation for security defense-in-depth.

While sandbox: false reduces renderer isolation, the security risk is mitigated here because:

  1. The preload script safely exposes only IPC channels—no dangerous Node.js APIs (fs, child_process, os, etc.) are exposed
  2. The URL is sourced internally from webServiceManager.getStatus().url, which returns localhost

However, add explicit URL validation in the handler to enforce localhost-only loading as a defensive practice:

const isLocalhost = url.startsWith('http://localhost:') || url.startsWith('http://127.0.0.1:');
if (!isLocalhost) {
  console.error('[WindowHandlers] URL must be localhost:', url);
  return false;
}

This prevents potential issues if the URL source ever changes.

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

In `@src/main/ipc/handlers/windowHandlers.ts` around lines 87 - 94, Add explicit
localhost-only URL validation in the window creation/loading handler (use the
same URL from webServiceManager.getStatus().url) before calling
BrowserWindow.loadURL or similar; check that the URL starts with
'http://localhost:' or 'http://127.0.0.1:' (e.g., const isLocalhost =
url.startsWith('http://localhost:') || url.startsWith('http://127.0.0.1:');) and
if not, log an error (console.error or processLogger.error) and return false to
abort loading; update the handler that creates/loads the BrowserWindow (the
function that references preloadPath/webPreferences and calls loadURL) to
include this guard.
src/main/llm-installation-manager.ts-126-140 (1)

126-140: ⚠️ Potential issue | 🟠 Major

Remove shell: true from Windows spawn call to prevent command injection.

The prompt variable is constructed from fileName (line 120), which is extracted from the manifest's llmPrompt path. While path.basename() appears to extract just the filename, it preserves shell metacharacters (backticks, $(), etc.). With shell: true, these characters would be interpreted by cmd.exe on Windows, allowing command injection if an attacker can control or modify the manifest file.

Since the command and arguments are already separated as an array, shell: false is both safer and functionally equivalent. Remove the shell: true option to eliminate the shell interpretation vector.

Recommended fix
       if (platform === 'win32') {
         // Windows: Directly spawn claude process
         try {
           log.info('[LlmInstallationManager] Windows command:', `claude "${prompt}"`);
           spawn('claude', [prompt], {
             detached: true,
             stdio: 'ignore',
             cwd: promptDir,
-            shell: true,
           }).unref();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/llm-installation-manager.ts` around lines 126 - 140, In the Windows
branch of LlmInstallationManager where you spawn 'claude' using the prompt
variable (derived from fileName/llmPrompt), remove the shell: true option from
the spawn options so the command is executed without a shell; keep the argument
array ([prompt]), detached, stdio: 'ignore', cwd: promptDir and the .unref()
call intact to preserve behavior while preventing shell interpretation of prompt
(and thus command injection).
src/main/ipc/handlers/dataDirectoryHandlers.ts-113-120 (1)

113-120: ⚠️ Potential issue | 🟠 Major

Do not return success when appsettings.yml sync fails.

Line 113-118 and Line 180-185 log YAML update failures but still return success: true, which can hide config drift.

🛠️ Suggested fix
-      try {
-        const updatedVersions = await state.yamlConfigManager.updateAllDataDirs(dataDirPath);
-        log.info('[DataDirectoryHandlers] Updated appsettings.yml for versions:', updatedVersions);
-      } catch (error) {
-        log.warn('[DataDirectoryHandlers] Failed to update appsettings.yml:', error);
-      }
-
-      return { success: true };
+      try {
+        const updatedVersions = await state.yamlConfigManager.updateAllDataDirs(dataDirPath);
+        log.info('[DataDirectoryHandlers] Updated appsettings.yml for versions:', updatedVersions);
+      } catch (error) {
+        log.warn('[DataDirectoryHandlers] Failed to update appsettings.yml:', error);
+        return {
+          success: false,
+          error: 'Data directory updated, but appsettings.yml synchronization failed'
+        };
+      }
+
+      return { success: true };

Also applies to: 180-187

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

In `@src/main/ipc/handlers/dataDirectoryHandlers.ts` around lines 113 - 120, The
handler currently always returns { success: true } even when
state.yamlConfigManager.updateAllDataDirs(...) fails; change the try/catch in
the handler(s) so that on catch you do not return success: true — either rethrow
the error or return { success: false, error: String(error) } (include the error
message) instead, and ensure both occurrences that call updateAllDataDirs (the
block around updateAllDataDirs(...) and the second block at lines ~180-187) are
updated to propagate failure rather than reporting success; keep log.warn(...)
but make the response reflect the failure.
src/main/ipc/handlers/packageSourceHandlers.ts-63-63 (1)

63-63: ⚠️ Potential issue | 🟠 Major

Type config IPC payloads explicitly to avoid any leakage.

Lines 63 and 107 accept untyped config payloads on critical config mutation and validation paths, violating strict type safety requirements. Existing type definitions for PackageSourceConfig (and variants LocalFolderConfig, GitHubReleaseConfig, HttpIndexConfig) are available in src/main/package-sources/package-source.ts and can be imported and applied directly.

🛠️ Suggested fix
import { ipcMain, BrowserWindow } from 'electron';
import { VersionManager } from '../../version-manager.js';
+import type { PackageSourceConfig } from '../../package-sources/package-source.js';

// Module state
interface PackageSourceHandlerState {
  versionManager: VersionManager | null;
  mainWindow: BrowserWindow | null;
}

...

-  ipcMain.handle('package-source:set-config', async (_, config) => {
+  ipcMain.handle('package-source:set-config', async (_, config: PackageSourceConfig) => {
    if (!state.versionManager) {
      return { success: false, error: 'Version manager not initialized' };
    }
    try {
      const success = await state.versionManager.setSourceConfig(config);

And for line 107:

-  ipcMain.handle('package-source:validate-config', async (_, config) => {
+  ipcMain.handle('package-source:validate-config', async (_, config: PackageSourceConfig) => {
    if (!state.versionManager) {
      return { valid: false, error: 'Version manager not initialized' };
    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/ipc/handlers/packageSourceHandlers.ts` at line 63, Import the
explicit config types (PackageSourceConfig and/or the specific variants
LocalFolderConfig, GitHubReleaseConfig, HttpIndexConfig) from the package-source
type definitions and annotate the IPC handler parameters so the untyped `config`
is no longer `any`: change the handler signature for the
'package-source:set-config' ipcMain.handle from async (_, config) => to async
(_event, config: PackageSourceConfig) => (or the appropriate union of variant
types) and do the same for the validation handler on the validation path (the
handler around line 107) so both mutation and validation code paths use the
imported, explicit types.
src/main/ipc/handlers/webServiceHandlers.ts-130-147 (1)

130-147: ⚠️ Potential issue | 🟠 Major

Return shape mismatch: { success: result } incorrectly nests a StartResult object.

Line 146 wraps the entire StartResult object (which contains { success: boolean; resultSession; parsedResult; url?; port? }), but the renderer expects { success: boolean; error?: {...}; warning?: {...} }. This creates { success: StartResult } instead of a flat boolean, breaking the IPC contract and violating TypeScript strict mode type safety.

🛠️ Suggested fix
-      return { success: result };
+      return result;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/ipc/handlers/webServiceHandlers.ts` around lines 130 - 147, The
handler currently returns the entire StartResult object as { success: result }
which nests StartResult instead of returning the flat IPC shape; change the
return to extract and return result.success as a boolean and map any
error/warning fields from StartResult into the top-level response (e.g., return
{ success: result.success, error: result.error?.… , warning: result.warning?.… }
or omit when absent), ensuring the returned shape matches the renderer contract
and update related typings if necessary; locate the call site around
state.webServiceManager.start() / StartResult and adjust the return value and
types accordingly.
src/main/main.ts-2148-2154 (1)

2148-2154: ⚠️ Potential issue | 🟠 Major

Apply data-directory changes atomically.

configManager.setDataDirectoryPath(dataDirPath) runs before pathManager.setDataDirectory(dataDirPath). If the second step throws, persisted config and in-memory state diverge.

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

In `@src/main/main.ts` around lines 2148 - 2154, Make the data-directory update
atomic: set the in-memory value on PathManager first and only persist to
electron-store after that succeeds, or if you prefer to persist first then
rollback the in-memory state on failure; specifically, call
pathManager.setDataDirectory(dataDirPath) before
configManager.setDataDirectoryPath(dataDirPath) and wrap the persistence step in
a try/catch that on failure restores the previous PathManager value (capture
previous via pathManager.getDataDirectory() or equivalent) and logs the error so
persisted and in-memory state cannot diverge.
src/main/main.ts-24-39 (1)

24-39: ⚠️ Potential issue | 🟠 Major

Modular IPC handlers are imported but not actually wired.

This file still registers the legacy monolithic handlers directly, and these new register*Handlers imports are never invoked. That leaves the refactor unintegrated and creates two drifting handler implementations.

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

In `@src/main/main.ts` around lines 24 - 39, The newly imported modular handlers
(registerWindowHandlers, registerServerHandlers, registerWebServiceHandlers,
registerVersionHandlers, registerDependencyHandlers,
registerPackageSourceHandlers, registerLicenseHandlers,
registerOnboardingHandlers, registerDataDirectoryHandlers,
registerRegionHandlers, registerLlmHandlers, registerRssHandlers,
registerDebugHandlers, registerViewHandlers) are never invoked; call each of
these register*Handlers during the app initialization where the legacy
monolithic handler registration currently happens (e.g., inside your main
init/start function) so the modular handlers are wired up, and remove or replace
the legacy monolithic registration to avoid duplicate handler implementations.
src/renderer/store/slices/dataDirectorySlice.ts-176-188 (1)

176-188: ⚠️ Potential issue | 🟠 Major

isLoadingStorage can stay true forever after errors.

fetchStorageInfo sets loading to true, but the catch path throws without resetting it. Add a finally reset (or rejected extraReducer) so the spinner always clears.

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

In `@src/renderer/store/slices/dataDirectorySlice.ts` around lines 176 - 188, In
fetchStorageInfo ensure isLoadingStorage is cleared on all paths: after
dispatch(dataDirectorySlice.actions.setLoadingStorage(true)) add a finally (or
an extraReducer for the rejected case) that dispatches
dataDirectorySlice.actions.setLoadingStorage(false) so the spinner is always
reset; keep existing error handling that dispatches setSaveError and re-throws,
but guarantee setLoadingStorage(false) runs whether the await calls in
fetchStorageInfo, window.electronAPI.dataDirectory.get(), or
window.electronAPI.dataDirectory.getStorageInfo(currentPath) succeed or fail.
src/main/path-manager.ts-415-419 (1)

415-419: ⚠️ Potential issue | 🟠 Major

getStorageInfo computes total capacity from directory metadata.

Using directory stat.size as disk total produces invalid available and usedPercentage values. Compute volume totals from filesystem-level stats (or return unknown totals) instead of deriving from directory entry size.

Also applies to: 423-430

src/renderer/store/slices/dataDirectorySlice.ts-100-103 (1)

100-103: ⚠️ Potential issue | 🟠 Major

Save/restore errors are being cleared immediately.

Failure paths set saveError, then call setSaving(false), but setSaving resets saveError to null. The UI loses the actual failure message.

🛠️ Proposed fix
 setSaving: (state, action: PayloadAction<boolean>) => {
   state.isSaving = action.payload;
-  state.saveError = null;
 },
- dispatch(dataDirectorySlice.actions.setSaveError(errorMessage));
- dispatch(dataDirectorySlice.actions.setSaving(false));
+ dispatch(dataDirectorySlice.actions.setSaveError(errorMessage));
- dispatch(dataDirectorySlice.actions.setSaveError(errorMessage));
- dispatch(dataDirectorySlice.actions.setSaving(false));
+ dispatch(dataDirectorySlice.actions.setSaveError(errorMessage));

Also applies to: 165-167, 218-220

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

In `@src/renderer/store/slices/dataDirectorySlice.ts` around lines 100 - 103, The
setSaving reducer in dataDirectorySlice (setSaving) currently sets
state.isSaving and unconditionally clears state.saveError, which wipes out
errors set by failure paths; change setSaving to only update state.isSaving and
do not modify state.saveError, and instead rely on the dedicated setSaveError
reducer to clear or set errors when appropriate—update any callsites that expect
setSaving(false) to clear errors to call setSaveError(null) explicitly if they
truly intend to clear the error; apply the same change pattern to the other
similar reducers/usages in this slice (the other setSaving-like occurrences).
src/main/main.ts-1997-2000 (1)

1997-2000: ⚠️ Potential issue | 🟠 Major

Guard startup against invalid persisted data-directory values.

pathManager.setDataDirectory(...) can throw; this call is currently unguarded during startup. A stale/invalid stored path can abort app launch instead of falling back safely.

🛠️ Proposed hardening
- if (dataDirectoryPath) {
-   pathManager.setDataDirectory(dataDirectoryPath);
- }
+ if (dataDirectoryPath) {
+   try {
+     pathManager.setDataDirectory(dataDirectoryPath);
+   } catch (error) {
+     const fallbackPath = pathManager.getDefaultDataDirectory();
+     log.warn('[Config] Invalid data directory, falling back to default:', error);
+     pathManager.setDataDirectory(fallbackPath);
+     configManager.setDataDirectoryPath(fallbackPath);
+     dataDirectoryPath = fallbackPath;
+   }
+ }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/main.ts` around lines 1997 - 2000, Wrap the call to
pathManager.setDataDirectory(dataDirectoryPath) in a try/catch that validates
dataDirectoryPath before and catches any thrown errors from setDataDirectory; on
error, log the failure (including the error) and do not rethrow so startup can
continue with the default/fallback path, and ensure any subsequent use of
pathManager expects the default state if setDataDirectory failed.
src/preload/index.ts-57-68 (1)

57-68: ⚠️ Potential issue | 🟠 Major

Fix packageSource interface to match the runtime implementation.

The interface (lines 57-68) declares saveConfig, removeSource, and getCurrentConfig, but the runtime object (lines 146-162) uses setConfig instead of saveConfig and omits removeSource and getCurrentConfig entirely. This creates a type-safety gap where code compiles against a contract that doesn't exist at runtime.

  • Replace saveConfig with setConfig in the interface definition
  • Remove removeSource and getCurrentConfig from the interface, or implement them in the runtime
  • Consider adding onConfigChange to the interface if it is a stable API
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/preload/index.ts` around lines 57 - 68, The packageSource interface does
not match the runtime implementation: replace the saveConfig member with
setConfig (to match the runtime's setConfig function), remove the removeSource
and getCurrentConfig members from the interface unless you implement
corresponding runtime functions, and optionally add an onConfigChange member if
you plan to expose that stable API; update the packageSource interface
signatures (getConfig, getAllConfigs, setConfig, switchSource, validateConfig,
scanFolder, fetchGithub, fetchHttpIndex and optionally onConfigChange) so they
exactly match the runtime object’s method names and return types.
src/main/ipc/handlers/versionHandlers.ts-273-276 (1)

273-276: ⚠️ Potential issue | 🟠 Major

Expose a typed method in VersionManager to update the default channel instead of accessing private properties.

The code uses (state.versionManager as any).packageSourceConfigManager to bypass type safety and access a private property. Add a public method to VersionManager like updateSourceDefaultChannel(sourceId: string, channel: string): void to encapsulate this operation and maintain strict type safety.

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

In `@src/main/ipc/handlers/versionHandlers.ts` around lines 273 - 276, Add a typed
public method on VersionManager (e.g., updateSourceDefaultChannel(sourceId:
string, channel: string): void) that internally calls
this.packageSourceConfigManager.updateSource(sourceId, { defaultChannel: channel
}); then replace the casty usage in the handler (state.versionManager as
any).packageSourceConfigManager.updateSource(...) with
state.versionManager.updateSourceDefaultChannel(currentConfig.id, channel) so
callers use the new public API and no private property is accessed directly.
src/preload/index.ts-84-85 (1)

84-85: ⚠️ Potential issue | 🟠 Major

Update restoreDefault return type to discriminated union for type safety.

The implementation correctly returns either { success: true; path: string } or { success: false; error: string }, but the type definition incorrectly declares { success: boolean; path: string }, making path always required and omitting the error field. Renderer code accesses result.error when handling failures, which violates strict type checking.

🛠️ Proposed type fix
- restoreDefault: () => Promise<{ success: boolean; path: string }>;
+ restoreDefault: () => Promise<
+   | { success: true; path: string }
+   | { success: false; error: string }
+ >;

As per coding guidelines: src/**/*.{ts,tsx}: Maintain strict type safety with TypeScript strict mode enabled.

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

In `@src/preload/index.ts` around lines 84 - 85, The exported preload type for
restoreDefault is wrong: update the declaration of restoreDefault in
src/preload/index.ts to use a discriminated union matching the implementation
(either { success: true; path: string } or { success: false; error: string }) so
callers can narrow on success and safely access path or error; leave openPicker
as-is but ensure restoreDefault's signature replaces the current { success:
boolean; path: string } shape with the two-branch union using the success
boolean literal types.
🟡 Minor comments (3)
src/renderer/i18n/index.ts-53-56 (1)

53-56: ⚠️ Potential issue | 🟡 Minor

Fix misleading namespace debug output.

Line 56 currently logs language keys as namespaces. Object.keys(i18n.store.data) returns locales (e.g., en-US), not namespace names.

Suggested fix
   .then(() => {
     // Log successful initialization for debugging
     console.log('[i18n] Initialized successfully');
     console.log('[i18n] Current language:', i18n.language);
-    console.log('[i18n] Available namespaces:', Object.keys(i18n.store.data));
+    const currentNamespaces = Object.keys(i18n.store.data[i18n.language] ?? {});
+    console.log('[i18n] Available namespaces:', currentNamespaces);
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/i18n/index.ts` around lines 53 - 56, The debug message is
logging locales instead of namespaces; change the third console.log so it
derives namespaces from i18n.store.data by fetching the namespace keys for the
active locale (e.g., use Object.keys(i18n.store.data[i18n.language] || {})) or
compute the union of namespace keys across locales, and log that result; update
the log in the i18n initialization block that references i18n and
i18n.store.data so it prints actual namespace names rather than locale keys.
src/main/llm-installation-manager.ts-141-152 (1)

141-152: ⚠️ Potential issue | 🟡 Minor

Incomplete escaping for osascript may allow injection.

Line 144 escapes " and $, but other characters like backticks, single quotes, or backslashes could still cause issues in the AppleScript/shell context. Consider using a more robust escaping approach or validating the prompt content.

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

In `@src/main/llm-installation-manager.ts` around lines 141 - 152, The macOS
branch in LlmInstallationManager builds constructedCommand using escapedPrompt
but only escapes double quotes and dollar signs, leaving backticks, single
quotes and backslashes vulnerable to injection when calling execAsync; fix by
robustly sanitizing or safely quoting the prompt before embedding in the
osascript string (e.g., replace/backslash-escape backslashes, single quotes,
backticks and newlines or use a safe quoting helper), or validate/whitelist the
prompt content, and update the construction of constructedCommand (and
escapedPrompt) so the final osascript string is safe before calling execAsync.
src/main/ipc/handlers/debugHandlers.ts-37-50 (1)

37-50: ⚠️ Potential issue | 🟡 Minor

Silent no-op when configManager is null.

If state.configManager is null, the handler silently succeeds without actually persisting or broadcasting the debug mode. Consider returning an error to inform the caller:

🛡️ Proposed fix
   ipcMain.handle('set-debug-mode', async (_, mode: { ignoreDependencyCheck: boolean }) => {
     try {
+      if (!state.configManager) {
+        return {
+          success: false,
+          error: 'ConfigManager not initialized',
+        };
+      }
       const storeKey = 'debugMode';
-      state.configManager?.getStore().set(storeKey, mode);
+      state.configManager.getStore().set(storeKey, mode);
       state.mainWindow?.webContents.send('debug-mode-changed', mode);
       return { success: true };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/ipc/handlers/debugHandlers.ts` around lines 37 - 50, The handler
registered with ipcMain.handle('set-debug-mode') currently silently no-ops if
state.configManager is null; update the handler in debugHandlers.ts to
explicitly check state.configManager before calling getStore(): if it's missing,
return { success: false, error: 'configManager not available' } (or similar)
instead of proceeding, and only call state.configManager.getStore().set(...) and
state.mainWindow?.webContents.send('debug-mode-changed', mode) when
configManager exists; keep the existing try/catch for other errors but ensure
the null-configManager case is handled and reported to the caller.
🧹 Nitpick comments (14)
src/renderer/components/SettingsPage.tsx (1)

5-5: Use renderer path alias for this import.

Please switch this relative renderer import to @/* alias for consistency with project rules.

Suggested fix
-import { DataDirectorySettings } from './settings/DataDirectorySettings';
+import { DataDirectorySettings } from '@/components/settings/DataDirectorySettings';

As per coding guidelines src/renderer/**/*.{ts,tsx}: Use @/* path aliases for imports within the renderer process.

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

In `@src/renderer/components/SettingsPage.tsx` at line 5, In SettingsPage.tsx
replace the relative import of DataDirectorySettings with the renderer path
alias; specifically change the './settings/DataDirectorySettings' import used in
the SettingsPage component to '@/components/settings/DataDirectorySettings' so
it follows the src/renderer `@/`* alias convention.
src/main/ipc/handlers/rssHandlers.ts (1)

17-59: Make handler registration idempotent.

If this register function is called more than once, ipcMain.handle can collide on existing channels.

Suggested fix
 export function registerRssHandlers(manager: RSSFeedManager | null): void {
   rssFeedManager = manager;
+  ipcMain.removeHandler('rss-get-feed-items');
+  ipcMain.removeHandler('rss-refresh-feed');
+  ipcMain.removeHandler('rss-get-last-update');

   // RSS get feed items handler
   ipcMain.handle('rss-get-feed-items', async () => {
#!/bin/bash
# Verify call sites and absence/presence of de-registration guards
rg -nP "registerRssHandlers\s*\(" --type=ts -C2
rg -nP "ipcMain\.removeHandler\('rss-(get-feed-items|refresh-feed|get-last-update)'\)" --type=ts -C2
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/ipc/handlers/rssHandlers.ts` around lines 17 - 59, The
registerRssHandlers function registers ipcMain.handle on three channels and will
collide if called multiple times; make it idempotent by removing any existing
handlers before registering: call ipcMain.removeHandler for
'rss-get-feed-items', 'rss-refresh-feed', and 'rss-get-last-update' at the start
of registerRssHandlers (while still assigning rssFeedManager = manager) so each
invocation first clears previous handlers and then re-registers the handlers
that call rssFeedManager.getFeedItems, rssFeedManager.refreshFeed, and
rssFeedManager.getLastUpdateTime; ensure error handling remains unchanged.
src/main/ipc/handlers/viewHandlers.ts (3)

1-1: Remove unused BrowserWindow import.

BrowserWindow is imported but never used in this module.

🧹 Proposed fix
-import { ipcMain, BrowserWindow } from 'electron';
+import { ipcMain } from 'electron';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/ipc/handlers/viewHandlers.ts` at line 1, Remove the unused
BrowserWindow import from the top-level import statement in viewHandlers.ts:
update the import that currently reads "import { ipcMain, BrowserWindow } from
'electron';" to only import the actually used symbol(s) (e.g., "ipcMain") so
BrowserWindow is no longer imported but leave other imports unchanged.

38-73: Consider using shared HandlerResponse types for consistency.

The switch-view handler returns ad-hoc response objects with varying shapes (reason, canStart, url, error). Using the shared HandlerResponse<T> from ./types.ts would provide consistency across handlers and better type safety on the renderer side.

This is a low-priority suggestion since the current approach works, but aligning with the shared types would improve maintainability.

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

In `@src/main/ipc/handlers/viewHandlers.ts` around lines 38 - 73, The handler for
ipcMain.handle('switch-view') currently returns ad-hoc shapes; import and use
the shared HandlerResponse<T> from ./types.ts and annotate the handler's return
type accordingly (e.g., HandlerResponse<{ url?: string; canStart?: boolean }>),
then replace the various return objects so they conform to that type (map
'success'/'reason'/'error' into the HandlerResponse fields and include url or
canStart in the data payload when present); ensure the error catch returns a
HandlerResponse with success: false and a standardized error/message field and
that the webServiceManager.getStatus branch returns success: true with data.url
when running.

75-79: Stub implementation returns hardcoded value.

get-current-view always returns 'system' regardless of actual view state. The comment acknowledges this needs tracking, but consider adding a TODO or tracking issue to avoid forgetting.

Would you like me to open an issue to track implementing proper view state tracking?

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

In `@src/main/ipc/handlers/viewHandlers.ts` around lines 75 - 79, The IPC handler
ipcMain.handle('get-current-view') currently returns the hardcoded string
'system'; replace this stub with real view-state tracking by adding a mutable
state (e.g., currentView) in the same module and a complementary
ipcMain.handle('set-current-view') or ipcMain.on('view-changed') handler that
updates currentView when the renderer notifies the main process, then have
get-current-view return currentView (and add a TODO/comment referencing an issue
if you want to postpone full implementation). Ensure you reference the existing
handler name ('get-current-view') and add the update handler name
('set-current-view' or 'view-changed') so future reviewers can find the change.
src/main/config.ts (1)

128-168: Inconsistent accessor patterns for directory getters.

getShutdownDirectory, getRecordingDirectory, and getLogsDirectory use this.store.get() with type assertions, while getDataDirectoryPath uses the typed this.get() method. Use the consistent pattern for type safety:

♻️ Proposed fix for consistency
   getShutdownDirectory(): string | undefined {
-    return this.store.get('shutdownDirectory') as string | undefined;
+    return this.get('shutdownDirectory');
   }
   
   getRecordingDirectory(): string | undefined {
-    return this.store.get('recordingDirectory') as string | undefined;
+    return this.get('recordingDirectory');
   }
   
   getLogsDirectory(): string | undefined {
-    return this.store.get('logsDirectory') as string | undefined;
+    return this.get('logsDirectory');
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/config.ts` around lines 128 - 168, The getters getShutdownDirectory,
getRecordingDirectory, and getLogsDirectory use this.store.get(...) with type
assertions which is inconsistent with getDataDirectoryPath; change each to use
the class helper this.get('key') to get a properly typed value (e.g.,
this.get('shutdownDirectory')), remove the manual type assertions, and keep the
corresponding setters setShutdownDirectory, setRecordingDirectory, and
setLogsDirectory unchanged; update the three getter implementations so they
return string | undefined via this.get(...) for consistent type safety.
src/renderer/components/settings/DataDirectorySettings.tsx (2)

62-67: Intentionally omitting localPath from dependency array needs clarification.

The effect syncs localPath with the global path, but localPath is intentionally excluded to prevent infinite loops. Add a comment to clarify this intentional omission, or use a ref to track the previous path value:

// eslint-disable-next-line react-hooks/exhaustive-deps -- localPath excluded to prevent sync loops
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/settings/DataDirectorySettings.tsx` around lines 62 -
67, In DataDirectorySettings, the useEffect that syncs localPath with path
deliberately omits localPath from the dependency array to avoid update loops;
update the code to make this intent explicit by adding a clarifying comment
above the effect (or use a ref to track previous path if preferred) and, if
keeping the omission, add an eslint disable comment such as:
eslint-disable-next-line react-hooks/exhaustive-deps with an explanation
referencing the useEffect in DataDirectorySettings to prevent accidental future
changes.

94-108: Add type narrowing for thunk result payload.

Line 101 accesses validationResult?.payload?.isValid without proper type narrowing. The thunk result type could be fulfilled or rejected, and the payload shape differs:

🛡️ Proposed fix with type guard
       if (result && !result.canceled && result.filePath) {
-        // Validate the selected path
         const validationResult = await dispatch(validatePath(result.filePath));
-        if (validationResult?.payload?.isValid) {
+        if (
+          validatePath.fulfilled.match(validationResult) &&
+          validationResult.payload?.isValid
+        ) {
           setLocalPath(result.filePath);
         }
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/settings/DataDirectorySettings.tsx` around lines 94 -
108, The code in handleBrowse reads validationResult?.payload?.isValid without
narrowing the thunk result; update the logic that calls validatePath so you only
read payload when the thunk succeeded—either by calling
unwrapResult(validatePath(...)) (from `@reduxjs/toolkit`) or by checking
validationResult?.type === 'fulfilled' before accessing
validationResult.payload.isValid, then call setLocalPath(result.filePath) only
when the payload.isValid is true; ensure any rejected result path is handled (or
ignored) to satisfy TypeScript.
src/main/ipc/handlers/llmHandlers.ts (1)

99-117: Use path.join for cross-platform path construction.

Line 103 uses string template for path concatenation (${versionPath}/manifest.json). While this often works on Windows with Node.js APIs, using path.join is more robust and idiomatic:

♻️ Proposed fix
+import path from 'node:path';

At line 103:

-      const manifestPath = `${versionPath}/manifest.json`;
+      const manifestPath = path.join(versionPath, 'manifest.json');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/ipc/handlers/llmHandlers.ts` around lines 99 - 117, Replace the
string-template path concatenation with path.join to build manifest paths
cross-platform: inside the ipcMain.handle('llm:get-manifest-path') callback
where you call PathManager.getInstance() and getInstalledVersionPath(versionId),
import Node's path module if not already present, compute manifestPath using
path.join(versionPath, 'manifest.json') instead of
`${versionPath}/manifest.json`, and keep the same return/error handling in the
handler.
src/main/ipc/handlers/types.ts (1)

29-32: Consider a tuple-based signature for heterogeneous arguments.

The ...args: T[] rest parameter constrains all arguments to be the same type T. Handlers with multiple arguments of different types (e.g., (event, path: string, options: object)) won't type-check correctly with this signature.

If handlers need heterogeneous args, consider:

export type IpcHandler<TArgs extends unknown[] = unknown[], R = unknown> = (
  event: Electron.IpcMainInvokeEvent,
  ...args: TArgs
) => Promise<R>;

However, if all current handlers use single or homogeneous arguments, the current definition is acceptable.

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

In `@src/main/ipc/handlers/types.ts` around lines 29 - 32, Change the IpcHandler
type to support heterogeneous argument tuples: replace the single-type generic T
and the rest param ...args: T[] in the IpcHandler definition with a tuple-style
generic (e.g., TArgs extends unknown[] = unknown[]) and use ...args: TArgs so
handlers can declare varied argument types; update the exported type name
IpcHandler<TArgs extends unknown[] = unknown[], R = unknown> and the function
signature to accept event: Electron.IpcMainInvokeEvent, ...args: TArgs =>
Promise<R>.
src/main/ipc/handlers/debugHandlers.ts (1)

53-62: Type assertion without validation could cause runtime issues.

Line 56 uses as { ignoreDependencyCheck: boolean } without validating the stored value's shape. If the config store contains malformed data (e.g., from a schema migration), this could propagate invalid data to callers.

Consider adding a runtime check or using a validation library:

🛡️ Proposed defensive check
   ipcMain.handle('get-debug-mode', async () => {
     try {
       const storeKey = 'debugMode';
-      const mode = state.configManager?.getStore().get(storeKey, { ignoreDependencyCheck: false }) as { ignoreDependencyCheck: boolean };
-      return mode;
+      const stored = state.configManager?.getStore().get(storeKey, { ignoreDependencyCheck: false });
+      const mode = typeof stored === 'object' && stored !== null && typeof (stored as any).ignoreDependencyCheck === 'boolean'
+        ? stored as { ignoreDependencyCheck: boolean }
+        : { ignoreDependencyCheck: false };
+      return mode;
     } catch (error) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/ipc/handlers/debugHandlers.ts` around lines 53 - 62, The handler
registered with ipcMain.handle('get-debug-mode') currently casts the retrieved
value using "as { ignoreDependencyCheck: boolean }" without validating it;
replace that unsafe assertion by fetching via
state.configManager?.getStore().get(storeKey) and perform a runtime shape check
(ensure the result is an object and that typeof result.ignoreDependencyCheck ===
'boolean'); if the check passes return the object, otherwise log a warning and
return the safe default { ignoreDependencyCheck: false } — update the code paths
in the get-debug-mode handler (references: ipcMain.handle('get-debug-mode'),
state.configManager?.getStore().get) to implement this validation and fallback.
src/renderer/store/index.ts (1)

13-13: Use renderer alias import for the new slice.

Replace the relative import with the configured @/* alias pattern.

🛠️ Suggested fix
-import dataDirectoryReducer from './slices/dataDirectorySlice';
+import dataDirectoryReducer from '@/store/slices/dataDirectorySlice';

As per coding guidelines, src/renderer/**/*.{ts,tsx}: Use @/* path aliases for imports within the renderer process.

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

In `@src/renderer/store/index.ts` at line 13, Replace the relative import of the
new slice in src/renderer/store/index.ts with the renderer alias pattern; locate
the import that brings in dataDirectoryReducer and change it to use the
configured "@/..." alias (keeping the same module/specifier that points to the
slice file) so the import uses the renderer alias rather than a relative path.
src/main/ipc/handlers/serverHandlers.ts (1)

7-15: Use ServerStatus instead of raw string for status callbacks.

The file imports ServerStatus (line 2) but uses string for status callback parameters on lines 7, 14, and 25, weakening type safety. Replace all three occurrences with the imported ServerStatus type to maintain strict typing as required by your coding guidelines.

🛠️ Suggested fix
-let serverStatusSetter: ((status: string) => void) | null = null;
+let serverStatusSetter: ((status: ServerStatus) => void) | null = null;
...
 export function initServerHandlers(
   client: HagicoServerClient | null,
-  setStatus: (status: string) => void
+  setStatus: (status: ServerStatus) => void
 ): void {
...
 export function registerServerHandlers(
   client: HagicoServerClient | null,
-  setServerStatusFn?: (status: string) => void
+  setServerStatusFn?: (status: ServerStatus) => void
 ): void {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/ipc/handlers/serverHandlers.ts` around lines 7 - 15, The status
callback types are declared as raw strings; change them to use the imported
ServerStatus type for stronger typing: update the serverStatusSetter declaration
((status: string) => void) | null to ((status: ServerStatus) => void) | null,
change the setStatus parameter in initServerHandlers from (status: string) =>
void to (status: ServerStatus) => void, and update any other function/type
signatures in this file that accept a status string (e.g., the callback
referenced around line 25) so they all use ServerStatus consistently.
src/main/ipc/handlers/dependencyHandlers.ts (1)

139-142: Avoid duplicate dependency status recomputation after installs.

Both install flows run checkVersionDependencies(versionId) twice in sequence. Reuse a single computed result before emitting dependency-status-changed to avoid redundant I/O.

Also applies to: 219-222

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

In `@src/main/ipc/handlers/dependencyHandlers.ts` around lines 139 - 142, The code
calls state.versionManager.checkVersionDependencies(versionId) twice causing
redundant work; capture its result once (e.g., const updatedDependencies = await
state.versionManager.checkVersionDependencies(versionId)) and reuse that single
result for any subsequent logic and the
state.mainWindow?.webContents.send('dependency-status-changed',
updatedDependencies) emission in the install flows (the first occurrence near
the install handler and the second occurrence referenced later around the other
install flow), replacing the duplicate call with the stored variable so the
dependency status is computed only once.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 43ce3ac and ff331d7.

⛔ Files ignored due to path filters (25)
  • .claude/skills/ui-ux-pro-max/data/charts.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/colors.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/icons.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/landing.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/products.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/react-performance.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/stacks/astro.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/stacks/flutter.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/stacks/react-native.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/stacks/react.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/stacks/svelte.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/stacks/vue.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/styles.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/typography.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/ui-reasoning.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/ux-guidelines.csv is excluded by !**/*.csv
  • .claude/skills/ui-ux-pro-max/data/web-interface.csv is excluded by !**/*.csv
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (34)
  • .claude/skills/ui-ux-pro-max/SKILL.md
  • .claude/skills/ui-ux-pro-max/scripts/core.py
  • .claude/skills/ui-ux-pro-max/scripts/design_system.py
  • .claude/skills/ui-ux-pro-max/scripts/search.py
  • src/main/config-manager.ts
  • src/main/config.ts
  • src/main/ipc/handlers/dataDirectoryHandlers.ts
  • src/main/ipc/handlers/debugHandlers.ts
  • src/main/ipc/handlers/dependencyHandlers.ts
  • src/main/ipc/handlers/index.ts
  • src/main/ipc/handlers/licenseHandlers.ts
  • src/main/ipc/handlers/llmHandlers.ts
  • src/main/ipc/handlers/onboardingHandlers.ts
  • src/main/ipc/handlers/packageSourceHandlers.ts
  • src/main/ipc/handlers/regionHandlers.ts
  • src/main/ipc/handlers/rssHandlers.ts
  • src/main/ipc/handlers/serverHandlers.ts
  • src/main/ipc/handlers/types.ts
  • src/main/ipc/handlers/versionHandlers.ts
  • src/main/ipc/handlers/viewHandlers.ts
  • src/main/ipc/handlers/webServiceHandlers.ts
  • src/main/ipc/handlers/windowHandlers.ts
  • src/main/llm-installation-manager.ts
  • src/main/main.ts
  • src/main/path-manager.ts
  • src/main/web-service-manager.ts
  • src/preload/index.ts
  • src/renderer/components/SettingsPage.tsx
  • src/renderer/components/settings/DataDirectorySettings.tsx
  • src/renderer/i18n/index.ts
  • src/renderer/i18n/locales/en-US/pages.json
  • src/renderer/i18n/locales/zh-CN/pages.json
  • src/renderer/store/index.ts
  • src/renderer/store/slices/dataDirectorySlice.ts
💤 Files with no reviewable changes (4)
  • .claude/skills/ui-ux-pro-max/scripts/design_system.py
  • .claude/skills/ui-ux-pro-max/SKILL.md
  • .claude/skills/ui-ux-pro-max/scripts/search.py
  • .claude/skills/ui-ux-pro-max/scripts/core.py

Comment on lines +170 to +182
/**
* Get current language
*/
getCurrentLanguage(): string | undefined {
return this.store.get('language') as string | undefined;
}

/**
* Set current language
*/
setCurrentLanguage(language: string): void {
this.store.set('language', language);
}
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

Language accessors read/write wrong config key - schema mismatch.

getCurrentLanguage() and setCurrentLanguage() access 'language' at the root level, but AppConfig defines language under settings.language. This will not read/write the correct value:

🐛 Proposed fix
   /**
    * Get current language
    */
-  getCurrentLanguage(): string | undefined {
-    return this.store.get('language') as string | undefined;
+  getCurrentLanguage(): string {
+    return this.get('settings').language;
   }

   /**
    * Set current language
    */
   setCurrentLanguage(language: string): void {
-    this.store.set('language', language);
+    const current = this.get('settings');
+    this.set('settings', { ...current, language });
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/config.ts` around lines 170 - 182, The getCurrentLanguage and
setCurrentLanguage methods are using the wrong config key ('language' at root)
so they don't read/write AppConfig.settings.language; update
getCurrentLanguage() to return this.store.get('settings.language') as string |
undefined and update setCurrentLanguage(language: string) to call
this.store.set('settings.language', language) (ensure the methods remain typed
as string | undefined and void respectively).

Comment on lines +42 to +44
await licenseManager.saveLicense(licenseKey);
return { success: true };
} catch (error) {
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

Propagate saveLicense result instead of forcing success.

Line 43 always returns success, even when saveLicense returns { success: false, error } without throwing.

Suggested fix
     try {
-      await licenseManager.saveLicense(licenseKey);
-      return { success: true };
+      return await licenseManager.saveLicense(licenseKey);
     } catch (error) {
       console.error('Failed to save license:', error);
       return {
📝 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
await licenseManager.saveLicense(licenseKey);
return { success: true };
} catch (error) {
return await licenseManager.saveLicense(licenseKey);
} catch (error) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/ipc/handlers/licenseHandlers.ts` around lines 42 - 44, The handler
currently always returns { success: true } after calling
licenseManager.saveLicense, hiding failures; change it to await and store the
result from licenseManager.saveLicense(licenseKey) (e.g., const result = await
licenseManager.saveLicense(licenseKey)) and return that result directly so
callers receive { success: false, error } when saveLicense fails, while
preserving the existing catch block behavior for actual thrown errors.

Comment on lines +262 to +267
const invalidChars = /[<>:"|?*]/;
if (invalidChars.test(dirPath)) {
return {
isValid: false,
message: 'Path contains invalid characters: < > : " | ? *',
};
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

Windows absolute paths are incorrectly rejected in validation.

The invalid-character regex includes :, so valid Windows paths like C:\... fail validation.

🐛 Proposed fix (apply to both sync and async validators)
- const invalidChars = /[<>:"|?*]/;
- if (invalidChars.test(dirPath)) {
+ const pathWithoutDrive = process.platform === 'win32'
+   ? dirPath.replace(/^[a-zA-Z]:/, '')
+   : dirPath;
+ const invalidChars = /[<>:"|?*]/;
+ if (invalidChars.test(pathWithoutDrive)) {

Also applies to: 339-344

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

In `@src/main/path-manager.ts` around lines 262 - 267, The validator currently
rejects paths because invalidChars = /[<>:"|?*]/ includes ':' which breaks
Windows drive-letter paths (e.g., C:\...). Update both the sync and async
validators to allow a single colon as the drive-letter separator: instead of
blindly testing invalidChars against dirPath, first check for a Windows drive
prefix like /^[A-Za-z]:[\\/]/ and skip the colon check when that prefix is
present; alternatively construct the invalidChars regex to exclude ':' when
dirPath matches the drive-letter pattern. Adjust the logic around the
invalidChars test and the returned message so Windows absolute paths
(drive-letter) are accepted while other uses of ':' remain rejected.

Comment on lines +17 to +33
import type { Dispatch } from '@reduxjs/toolkit';

// App dispatch type combining all slice dispatches
export type AppDispatch = Dispatch<
| typeof import('./slices/webServiceSlice').actions
| typeof import('./slices/i18nSlice').actions
| typeof import('./slices/dependencySlice').actions
| typeof import('./slices/viewSlice').actions
| typeof import('./slices/packageSourceSlice').actions
| typeof import('./slices/licenseSlice').actions
| typeof import('./slices/onboardingSlice').actions
| typeof import('./slices/rssFeedSlice').actions
| typeof import('./slices/claudeConfigSlice').actions
| typeof import('./slices/agentCliSlice').actions
| typeof import('./slices/llmInstallationSlice').actions
| typeof import('./slices/dataDirectorySlice').actions
>;
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

🧩 Analysis chain

🏁 Script executed:

# First, let's see the file size and structure
wc -l src/renderer/store/index.ts

Repository: HagiCode-org/desktop

Length of output: 95


🏁 Script executed:

# Read the relevant sections: lines 1-50 to see the duplicate AppDispatch
head -50 src/renderer/store/index.ts | cat -n

Repository: HagiCode-org/desktop

Length of output: 2882


🏁 Script executed:

# Also read around line 179 to see the second declaration
sed -n '170,185p' src/renderer/store/index.ts | cat -n

Repository: HagiCode-org/desktop

Length of output: 600


🏁 Script executed:

# Verify the duplicate AppDispatch will cause TypeScript errors by checking if both declarations are active
rg -n "^export type AppDispatch" src/renderer/store/index.ts

Repository: HagiCode-org/desktop

Length of output: 155


🏁 Script executed:

# Check the Redux Toolkit version to confirm the pattern issue
rg -n "@reduxjs/toolkit" package.json | head -5

Repository: HagiCode-org/desktop

Length of output: 101


🏁 Script executed:

# Verify the path alias setup in the project
cat tsconfig.json | grep -A 10 "paths"

Repository: HagiCode-org/desktop

Length of output: 46


🏁 Script executed:

# Verify RTK dispatch typing best practice by checking imports of AppDispatch elsewhere
rg -n "AppDispatch" src/renderer --type ts --type tsx | head -10

Repository: HagiCode-org/desktop

Length of output: 91


🏁 Script executed:

# Check AppDispatch usage in the renderer directory
rg -n "AppDispatch" src/renderer | head -15

Repository: HagiCode-org/desktop

Length of output: 1628


Remove the duplicate and incorrect AppDispatch declaration on lines 20–33.

The file has two AppDispatch export declarations: the manual union type at lines 20–33 (incorrect for RTK dispatch typing) and the inferred type at line 179 (typeof store.dispatch). The inferred type is the correct pattern for Redux Toolkit dispatch typing and is used consistently throughout the codebase. Remove the manual declaration to avoid TypeScript compilation errors and maintain proper RTK patterns.

🛠️ Suggested fix
-import type { Dispatch } from '@reduxjs/toolkit';
-
-// App dispatch type combining all slice dispatches
-export type AppDispatch = Dispatch<
-  | typeof import('./slices/webServiceSlice').actions
-  | typeof import('./slices/i18nSlice').actions
-  | typeof import('./slices/dependencySlice').actions
-  | typeof import('./slices/viewSlice').actions
-  | typeof import('./slices/packageSourceSlice').actions
-  | typeof import('./slices/licenseSlice').actions
-  | typeof import('./slices/onboardingSlice').actions
-  | typeof import('./slices/rssFeedSlice').actions
-  | typeof import('./slices/claudeConfigSlice').actions
-  | typeof import('./slices/agentCliSlice').actions
-  | typeof import('./slices/llmInstallationSlice').actions
-  | typeof import('./slices/dataDirectorySlice').actions
->;
📝 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
import type { Dispatch } from '@reduxjs/toolkit';
// App dispatch type combining all slice dispatches
export type AppDispatch = Dispatch<
| typeof import('./slices/webServiceSlice').actions
| typeof import('./slices/i18nSlice').actions
| typeof import('./slices/dependencySlice').actions
| typeof import('./slices/viewSlice').actions
| typeof import('./slices/packageSourceSlice').actions
| typeof import('./slices/licenseSlice').actions
| typeof import('./slices/onboardingSlice').actions
| typeof import('./slices/rssFeedSlice').actions
| typeof import('./slices/claudeConfigSlice').actions
| typeof import('./slices/agentCliSlice').actions
| typeof import('./slices/llmInstallationSlice').actions
| typeof import('./slices/dataDirectorySlice').actions
>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/store/index.ts` around lines 17 - 33, There is a duplicate,
incorrect AppDispatch type declared as a manual union of slice action types;
remove that manual export (the union type block exported as AppDispatch) and
keep the existing inferred dispatch type using typeof store.dispatch (the
AppDispatch defined later from store.dispatch) so the code uses the correct
Redux Toolkit pattern; update any imports that referenced the removed manual
declaration to rely on the inferred AppDispatch from typeof store.dispatch.

@newbe36524 newbe36524 merged commit c96a9c7 into main Feb 26, 2026
4 checks passed
@newbe36524 newbe36524 deleted the feature/newgo branch February 26, 2026 15:29
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.

1 participant