Commit Graph

11 Commits

Author SHA1 Message Date
Gu JiaMing
02b2185482 feat(migration-v2-window): redesign V2 migration window (#16314)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Pleasurecruise <3196812536@qq.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Signed-off-by: gujiaming <52187003+AtomsH4@users.noreply.github.com>
2026-06-24 19:23:22 +08:00
fullex
4dc36fa20f feat(power): elevate PowerMonitorService into a system power hub
Rename PowerMonitorService -> PowerService and relocate it to
src/main/core/power/, turning the shutdown-only service into a complete,
self-contained power hub:

- Typed power events (suspend/resume/lock/unlock/power-source) with
  suspend/resume + power-source de-duplication; lock/unlock pass-through.
- Bounded, cross-platform shutdown barrier: preventDefault on macOS/Linux,
  blockShutdown via a minimal hidden window on Windows; handlers run under a
  5s hard timeout, then the app quits through the normal quit flow.
- Best-effort, ref-counted sleep prevention: preventSleep(reason) returns a
  Disposable and never throws (the provider degrades internally). The OS
  blocker engages only while a hold is held AND the user enabled
  app.power.prevent_sleep_when_busy. JobManager is the first registrant,
  acquiring a hold per execution attempt.

Add the app.power.prevent_sleep_when_busy preference (v2-only, no v1 source)
and a Settings -> General toggle. Service phase moved Background -> WhenReady.
2026-06-23 06:29:23 -07:00
fullex
29177ac5ba refactor(window-ipc): migrate window domain onto IpcApi
Collect the WindowManager caller-window controls (close / minimize / maximize /
unmaximize / set_full_screen / is_maximized / is_full_screen / get_init_data) and
the three directed window events (maximized_changed / fullscreen_changed / reused)
onto the IpcApi framework, replacing the legacy this.ipcHandle registrations, the
hand-written preload windowManager namespace, and the IpcChannel enums.

- Requests: ctx.senderId addresses the caller window (was getWindowIdByWebContents);
  fire-and-forget controls return void, queries keep their read type.
- Events: directed IpcApiService.send to the affected window, never broadcast.
- openSettings is a navigation concern misfiled under windowManager — moved to
  window.api.settings.openSettings (still legacy IPC, not yet on IpcApi).
- WindowManager_Open was dead (no renderer/preload consumer) — deleted.
- MainWindow_* (main-window singleton ops) intentionally left on legacy IPC.

Behavior verified equivalent before/after by independent review.
2026-06-15 03:35:25 -07:00
fullex
681534a2fa refactor(main-window-service): reduce external getMainWindow() usage via WindowManager broadcasts
Store BrowserWindow directly in a private field; mark public getMainWindow()
as @deprecated with a runtime warn. Migrate ~27 webContents.send call sites
across 20 services to WindowManager.broadcastToType(). Move main-window-
specific IPC handlers from the monolithic ipc.ts into MainWindowService and
deparameterize registerIpc(). Rename window/app channels to MainWindow_*
(values main-window:*), keeping the 5 sender-resolution channels as-is for
detached-tab reuse. Swap @DependsOn to WindowManager in migrated broadcasters.
2026-04-19 10:15:24 -07:00
fullex
bafed0d0e1 refactor(window-manager): migrate main window & fix macOS Dock semantics
- Rename WindowService -> MainWindowService (git mv, preserves blame/log)
- Register WindowType.Main in windowRegistry (singleton, showMode: manual);
  MainWindowService drives construction via wm.open and retains business
  logic only: IPC handlers, tray-aware close, crash recovery, show/toggle
- 50 call sites renamed (@DependsOn, application.get keys, imports)

WM Dock visibility refactored from visibility-based to existence-based:
- updateDockVisibility predicate drops isVisible/isMinimized check — a
  hidden main window must not remove the Dock icon (Cmd+W semantics)
- Add wm.setMacShowInDockByType(type, value) for tray-mode transitions;
  keyed by type so services can suppress Dock before the first instance
  exists (tray-on-launch path)
- Reduce triggers to window creation, destruction, and type-override
  changes; show/hide/minimize/restore no longer affect Dock state
- dockShouldBeVisible initializes true to match Electron's default

MainWindowService uses setMacShowInDockByType for tray-on-launch, close-
to-tray, and reopen-from-tray paths, replacing manual app.dock?.hide()
calls that raced against WM.

Docs updated across window-manager/{README, overview, platform,
api-reference, migration-guide}. New MainWindowService unit test covers
the close matrix (isQuitting, win/linux tray on/off, mac default, mac
tray on_close, fullscreen edge, Dock override assertions) and crash
recovery (first reload vs second-within-60s forceExit).
2026-04-19 06:52:03 -07:00
fullex
e5b3979652 fix(window-manager): let app.quit() complete for pooled windows during shutdown
Pooled lifecycle windows register a `close` handler that always calls
`event.preventDefault()` to recycle them to the pool. When `app.quit()`
fires `close` on every BrowserWindow, a single preventDefault is enough
for Electron to abort the quit chain, so `will-quit` never fires and
`Application.shutdown()` never runs. Once SelectionToolbar was migrated
into WindowManager's pooled lifecycle (57310618c), Ctrl+C started
leaving Electron as an orphan with `_isQuitting=true`, and a second
quit attempt from the menu was swallowed by the existing
`Already quitting` guard.

Bypass the pooled-close intercept and pool replenishment whenever
`application.isQuitting` is true — close events must reach their
native destination so `will-quit` can drive the normal
`Application.shutdown()` path. Also make `Application.quit()`
re-trigger `app.quit()` when already quitting, so a stalled first
attempt no longer traps the user into `kill -9`.

Covered by a new WindowManager test case asserting that pooled close
is not preventDefault'd while `application.isQuitting === true`.
2026-04-17 01:59:54 -07:00
fullex
9b451b87a9 refactor(paths): add @application path alias for main/core/application
Align with the existing @logger alias convention by introducing
@application as a short alias for src/main/core/application. This
reduces import verbosity across ~130 main-process files while keeping
4 intentional sub-path imports (@main/core/application/Application)
unchanged to preserve the vi.mock bypass mechanism in tests.

Configured in tsconfig.node.json and electron.vite.config.ts; Vitest
inherits the alias automatically.

Signed-off-by: fullex <0xfullex@gmail.com>
2026-04-11 22:06:00 -07:00
fullex
d92b9c7233 refactor(preboot): initialize path registry from preboot instead of bootstrap
Introduce `Application.initPathRegistry()` and invoke it from preboot in
`main/index.ts` (after the single-instance lock check, before
`crashReporter.start()`). `application.bootstrap()` no longer initializes
the path registry; it asserts the registry is already initialized and
fails fast with a clear error if not.

Why: the registry's only hard dependency is `app.setPath('userData')`
having completed, which `resolveUserDataLocation()` finishes in preboot.
Initializing from preboot decouples path registry lifetime from service
orchestration and exposes the timing contract as an explicit assertion
rather than a silent lazy init inside `bootstrap()`. This opens the door
for future preboot code (migration, rtk extraction, crashReporter) to
consume `application.getPath()` directly.

Also:
- `buildPathRegistry()` JSDoc now documents the constraint that new path
  keys must be resolvable in the preboot phase (no `whenReady`-only APIs,
  no dependencies on services being started).
- `getPath()` error message updated to point at `initPathRegistry()`.
- Global test mock gains a matching `initPathRegistry` stub.
- `preboot/README.md` and `paths/README.md` sync the new timing.

Signed-off-by: fullex <0xfullex@gmail.com>
2026-04-08 19:18:45 -07:00
fullex
b7741d311e refactor(paths): migrate file.ts helpers to application.getPath
Replace consumers of the seven legacy file.ts helpers (getTempDir,
getFilesDir, getNotesDir, getConfigDir, getCacheDir, getMcpDir,
getAppConfigDir) with application.getPath(), then delete the helpers.

All seven map to existing path registry keys — zero new keys needed.
Stable sub-path joins now use dedicated keys directly
(feature.dxt.uploads.temp, feature.preprocess.temp, feature.mcp.oauth,
feature.anthropic.oauth_file, feature.copilot.token_file,
feature.mcp.memory_file).

Auto-ensure on application.getPath() makes the existing fs.existsSync
+ fs.mkdirSync boilerplate redundant; removed initStorageDir from
FileStorage and ensureDirectories from DxtService and
BasePreprocessProvider.

Three top-level singletons (fileStorage, dxtService, copilotService)
are instantiated during the static import graph of src/main/index.ts
— before application.bootstrap() builds the path registry. Field
initializers and constructor assignments calling getPath() at
instantiation time would throw "PATHS not initialized". Each is
converted to a lazy getter (cached in CopilotService where the
resolution has an fs.existsSync side effect) with a TODO(v2) comment
marking it as a workaround for an underlying architectural issue:
these singletons should be migrated into the lifecycle system in a
follow-up.

Module-top-level const path expressions in AnthropicService
(CREDS_PATH) and mcpServers/memory.ts (defaultMemoryPath) are
wrapped in lazy getter functions for the same reason.

Test infrastructure: the unified application mock factory in
tests/__mocks__/main/application.ts now stubs getPath, so future
tests that instantiate services with path-dependent fields work
without per-test setup. The pre-existing ad-hoc mock in
MCPService.test.ts gets the same stub. Four describe blocks for the
deleted helpers are removed from file.test.ts (getAppConfigDir was
confirmed dead code, with no production callers).

src/main/utils/init.ts is intentionally untouched: its getConfigDir
is a private local function (not an import from file.ts), and the
file is @deprecated awaiting v2 BootConfig migration.

Signed-off-by: fullex <0xfullex@gmail.com>
2026-04-08 07:39:06 -07:00
suyao
dccdecd36e fix: add WindowService mock to test application factory
Tests calling application.get('WindowService') now get a minimal mock
instead of throwing "Unknown service: WindowService".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
2026-04-03 00:16:11 +08:00
fullex
1371cbe16f test: unify application mock with mockApplicationFactory
Replace ad-hoc vi.mock('@main/core/application') calls across test files
with a centralized mockApplicationFactory(overrides?) utility that provides
all registered services by default and supports per-service overrides.

- Add tests/__mocks__/main/DbService.ts (standalone DbService mock)
- Add tests/__mocks__/main/application.ts (unified factory)
- Simplify tests/main.setup.ts global mock
- Migrate 4 test files to use mockApplicationFactory
- Document unified mock system in CLAUDE.md and README.md

Signed-off-by: fullex <0xfullex@gmail.com>
2026-03-24 03:36:08 -07:00