Skip to content

refactor: modularize Electron main process architecture#6

Merged
Scofieldfree merged 15 commits intomainfrom
refactor/modularization
Jan 26, 2026
Merged

refactor: modularize Electron main process architecture#6
Scofieldfree merged 15 commits intomainfrom
refactor/modularization

Conversation

@Scofieldfree
Copy link
Contributor

Overview

This PR implements a comprehensive modularization of the Electron main process, transforming a monolithic ~814-line main.ts into a clean, maintainable architecture with ~180 lines in the entry file.

Changes Summary

Modular Architecture (78% code reduction in main.ts)

New Modules Created:

  • ipc/ - IPC handlers split by domain (config, session, history, log, updater, overlay)
  • audio/ - Audio processing pipeline (session management, converter, processor)
  • hotkey/ - Hotkey parsing and PTT behavior bindings
  • notification/ - System notification helpers
  • tray/ - Tray menu and localization refresh
  • window/ - Background/settings/overlay windows (already existed, documented)

i18n Enhancements

  • Real-time language sync - Language changes broadcast to all windows immediately
  • System locale auto-sync - App follows OS language when setting is 'system'
  • Language snapshot API - Type-safe language state propagation across processes

Documentation Updates

  • Comprehensive README files for all new modules
  • Updated CLAUDE.md with new directory structure
  • Updated electron/README.md with modularized components

Benefits

Maintainability - Each module has a single, well-defined responsibility
Testability - Isolated modules can be tested independently
Code Clarity - Descriptive logging prefixes and JSDoc comments throughout
Type Safety - Proper TypeScript types and dependency injection
Documentation - Every module has comprehensive README and JSDoc

Testing

Manual testing performed:

  • ✅ PTT recording start/stop works correctly
  • ✅ Audio conversion and ASR transcription functional
  • ✅ Text injection to active window works
  • ✅ Settings window opens and configuration changes persist
  • ✅ Language changes sync across all windows
  • ✅ System locale changes detected and applied when using 'system' setting

Breaking Changes

None. All functionality preserved with identical behavior.

Checklist

  • All commits follow Conventional Commits
  • TypeScript compilation passes
  • ESLint and Prettier checks pass
  • Documentation updated
  • Manual testing completed

Remove old agent-based system (.claude/agents/) and replace with modular
skills-based architecture (.claude/skills/). Add AI tool configurations for
Codex and OpenCode platforms.

This change enables:
- Reusable skill definitions across multiple AI tools
- Consistent git-commit functionality across Claude, Cursor, Codex, and OpenCode
- Enhanced branch management capabilities
- Local code review automation
- Skill creation utilities for extending the system
Remove deprecated skill documentation files from .codex/ and .opencode/
directories as part of the modularization refactoring. These files have been
migrated to the centralized .claude/skills/ structure.

Removed files:
- branch-manager skill (README, SKILL.md, examples, scripts)
- git-commit skill (SKILL.md)
- pr-generator skill (SKILL.md)

Also updates package-lock.json dependency metadata (peer flags).
…rate modules

Extract environment configuration and window management logic from main.ts into
dedicated modules for better separation of concerns and maintainability.

New modules:
- env.ts: Centralized runtime path configuration with initEnv() pattern
- window/background.ts: Background window lifecycle management
- window/index.ts: Barrel export for window module

Changes in main.ts:
- Replace inline environment setup with env.ts imports
- Replace createMainWindow() with createBackgroundWindow() from window module
- Use getBackgroundWindow() helper instead of direct variable access
- Add initEnv() call at app.whenReady() entry point

This refactoring aligns with the modularization effort to make the codebase
more maintainable and testable.
…lay and settings

Extract overlay and settings window management from main.ts into dedicated
modules within the window/ directory. This completes the modularization
refactoring started in the previous commit.

Changes in main.ts:
- Remove inline createSettingsWindow() function (140+ lines)
- Remove inline overlay functions (showOverlay, hideOverlay, updateOverlay, etc.)
- Import all window management functions from './window/index'
- Remove unused imports (screen, fileURLToPath, OverlayState type)

Changes in window/index.ts:
- Add exports for overlay module functions
- Add exports for settings module functions

Changes in window/overlay.ts:
- Implement overlay window lifecycle management
- Export functions: showOverlay, hideOverlay, updateOverlay, showErrorAndHide,
  sendAudioLevel, setOverlayIgnoreMouseEvents

Changes in window/settings.ts:
- Implement settings window lifecycle management
- Export functions: createSettingsWindow, getSettingsWindow,
  updateSettingsWindowTitle, focusSettingsWindow

This refactoring improves code organization by encapsulating all window
management logic in dedicated modules, making main.ts more focused on
application orchestration.
Extract all IPC handler logic from main.ts into dedicated modules under
electron/main/ipc/ for better separation of concerns and maintainability.

New structure:
- ipc/index.ts - Unified entry point with dependency injection
- ipc/config-handlers.ts - Configuration management IPC (3 channels)
- ipc/session-handlers.ts - Recording session IPC (5 channels)
- ipc/history-handlers.ts - History management IPC (3 channels)
- ipc/log-handlers.ts - Logging IPC (3 channels)
- ipc/updater-handlers.ts - Update management IPC (4 channels)
- ipc/overlay-handlers.ts - Overlay window IPC (3 channels)

Benefits:
- Type-safe dependency injection with explicit contracts
- Each handler module is independently testable
- Clear functional domain separation
- Comprehensive JSDoc documentation for all modules
- Preserves all 21 IPC channels with identical functionality
… modules

Extract audio processing pipeline and system tray management from main.ts
into dedicated modules for better separation of concerns and testability.

Audio module (electron/main/audio/):
- converter.ts - FFmpeg WebM to MP3 conversion with asar path handling
- session-manager.ts - Recording session state management
- processor.ts - Audio data processing pipeline (save → convert → ASR → inject)
- index.ts - Unified module exports

Tray module (electron/main/tray/):
- index.ts - System tray creation, menu building, and localization refresh

Benefits:
- Reduces main.ts from ~814 to ~327 lines (~60% reduction)
- Isolates audio processing logic for independent testing
- Centralizes tray management with proper lifecycle handling
- Preserves all functionality with detailed logging
- Clean dependency injection via initProcessor()
…ed module

Extract hotkey parsing, key code mapping, and PTT (push-to-talk) handler
from main.ts into electron/main/hotkey/ for better code organization.

New structure:
- hotkey/parser.ts - Electron Accelerator to uiohook code conversion
- hotkey/ptt-handler.ts - PTT hotkey registration with debounce logic
- hotkey/index.ts - Unified module exports

Benefits:
- Removes 208 lines from main.ts (further reduction to ~119 lines)
- Isolates hotkey parsing logic (comprehensive key mapping)
- Centralizes PTT registration with debouncing
- Cleaner dependency: main.ts now imports registerGlobalHotkeys()
- Preserves all functionality with logging
Extract system notification functionality from main.ts into
electron/main/notification/ for better code organization.

New structure:
- notification/index.ts - System notification wrapper with support check

Changes:
- Move showNotification() function to dedicated module
- Add showNotificationWithIcon() for future extensibility
- Remove unused Notification import from main.ts
- Reorganize imports in main.ts with descriptive section comments

Benefits:
- Centralizes notification logic
- Easier to extend notification features
- Cleaner import organization in main.ts
…tegy

Enhance main.ts initialization logic with better comments and smarter
window launch behavior for auto-start scenarios.

Changes:
- Add descriptive comments for each initialization step
- Implement smart settings window launch strategy:
  * Dev environment: Always open settings window
  * Production: Only open if not launched as hidden (e.g., from auto-start)
- Use app.getLoginItemSettings().wasOpenedAsHidden to detect launch mode

Benefits:
- Better code readability with clear section comments
- Improved user experience for auto-start scenarios
- Settings window doesn't appear when app starts hidden at login
Add language snapshot broadcast system to ensure language changes
propagate immediately to all renderer windows without restart.

Main process changes:
- Add getMainLanguageSnapshot() to capture current language state
- Add sendLanguageSnapshotToWindow() for per-window language updates
- Add broadcastLanguageSnapshot() for global language synchronization
- Register browser-window-created listener to auto-send snapshots
- Add APP_LANGUAGE_GET IPC handler for renderer queries

Renderer changes:
- Use getAppLanguage() API instead of full config for initialization
- Listen to APP_LANGUAGE_CHANGED IPC for real-time updates
- Update document.documentElement.lang on language change
- Simplify SettingsPage by removing duplicate resolveLanguage logic

Type system:
- Add LanguageSnapshot interface (setting, resolved, locale)
- Add APP_LANGUAGE_GET and APP_LANGUAGE_CHANGED IPC channels
- Type-safe language propagation across process boundary

Benefits:
- All windows stay in sync with language settings immediately
- New windows receive current language on creation
- No app restart required when changing language
- Single source of truth for language state in main process
Add automatic system locale detection and sync to keep app language
in sync with OS locale changes when using 'system' language setting.

Changes:
- Add syncSystemLocaleIfNeeded() to detect and sync system locale changes
- Track lastSystemLocale and lastResolvedLanguage for change detection
- Add browser-window-focus event listener to trigger locale checks
- Broadcast language snapshot and refresh localized UI on sync

Behavior:
- Only syncs when currentSetting === 'system'
- Checks for locale changes when any window gains focus
- Automatically updates all windows when system locale changes
- No-op when user has selected a specific language

Benefits:
- App language follows OS language automatically
- Seamless experience when user changes system language
- No manual language switching needed for system setting
Update all README files to document the new modular structure created
during the refactor/modularization effort.

Root documentation:
- CLAUDE.md - Add new module directories to project structure
- electron/README.md - Document new modularized components
- electron/main/README.md - Update to reflect main.ts changes
- src/README.md - Minor formatting update

New module documentation:
- audio/README.md - Audio pipeline (session, converter, processor)
- hotkey/README.md - Hotkey parsing + PTT bindings
- notification/README.md - System notification helpers
- tray/README.md - Tray menu + localization refresh
- window/README.md - Background/settings/overlay windows

This documentation ensures developers can quickly understand
the new modular architecture without reading source code.
@Scofieldfree
Copy link
Contributor Author

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

This is an excellent refactoring that achieves:

  • 78% reduction in main.ts (814 → 180 lines)
  • Clean modular architecture with 10+ dedicated modules
  • Type-safe dependency injection throughout
  • Comprehensive JSDoc documentation
  • Real-time i18n synchronization across windows
  • Smart window launch strategy for auto-start scenarios

All modules follow consistent patterns with proper error handling, logging prefixes, and lifecycle management. The i18n enhancements (language snapshot broadcast and system locale sync) are well-designed with proper type safety.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Add proper fallback chain for language resolution when getAppLanguage
fails or returns null in the renderer process.

Changes:
- Import resolveLanguage helper from shared i18n
- Add fallback to system language (navigator.language)
- Use resolveLanguage('system', systemLanguage) for consistent behavior
- Final fallback to DEFAULT_LANGUAGE if all else fails

This ensures the renderer always has a valid language even if
the main process IPC call fails during initialization.

Fixes potential undefined language issue on renderer startup.
@yexia553
Copy link
Contributor

@Scofieldfree 第一个Medium的感觉需要处理一下,还是有比较大的可能会遇到问题,第二个medium我不确定,不是特别懂,你评估一下

  • Medium: ipcMain.on(IPC_CHANNELS.AUDIO_DATA) 里直接调用异步 handleAudioData,未 await 且无 .catch,一旦抛错可能触发未处理的 Promise 拒绝并导致主进程不稳定;建议显式 void ...catch(...) 并记录错误/更新 HUD。
    electron/main/ipc/session-handlers.ts:64
  • Medium: 窗口模块用 path.join(__dirname, 'preload.cjs') 解析 preload;当前打包单文件还能正常,但若未来 main 构建启用 code-splitting/preserveModules,__dirname 可能变为子目录导致找不到 preload.cjs。建议统一
    通过 getMainDist()/app.getAppPath() 生成 preload 路径。electron/main/window/background.ts:32 electron/main/window/settings.ts:50 electron/main/window/overlay.ts:59
  • Low: 音频处理在主进程使用同步 I/O(写入与删除临时文件),长录音时可能阻塞热键/IPC 响应;建议改为 fs.promises 或在 worker 中处理。electron/main/audio/processor.ts:78 electron/main/audio/processor.ts:201
  • Low: IPC 通道名在多处硬编码('audio:data'、'error'、'set-ignore-mouse-events'、'transcription:result'),绕开了 IPC_CHANNELS 的类型约束,且 'error' 过于泛化易冲突;建议统一常量。electron/preload/
    preload.ts:112 electron/preload/preload.ts:139 electron/preload/preload.ts:161 electron/main/ipc/overlay-handlers.ts:51 electron/main/ipc/overlay-handlers.ts:58


## License

Same as the original code-review plugin.
Copy link
Contributor

Choose a reason for hiding this comment

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

@Scofieldfree 这个原本的License是什么?需要添加进来吗 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

直接删掉吧

- Delete .claude/skills/local-code-review/ directory (no longer needed)
- Add error handling for handleAudioData promise in session IPC handler
Replace manual __dirname calculation with centralized getMainDist()
function for consistent preload path resolution across all window types
(background, overlay, settings).
@Scofieldfree Scofieldfree merged commit ad24b95 into main Jan 26, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants