Promote the temporary boot profiler into a permanent, opt-in diagnostics facility gated by CS_DIAGNOSTICS (off by default, zero overhead when unset). Move it to src/main/core/diagnostics.ts so the db and lifecycle layers no longer cross-import a lifecycle-internal file.
Probes, all gated by the same flag: per-service init timing, phase service spans, event-loop lag, and a whenReady V8 CPU profile (carried over); slow DB queries, now covering interactive-transaction interiors and batches (not just client.execute); slow IPC handlers (BaseService.ipcHandle); window construction + ready-to-show latency (WindowManager); and slow DataApi requests (ApiServer).
DataApi request duration is consolidated to a single monotonic performance.now() measurement in handleRequest, computed only when enabled; the redundant Date.now() duration in MiddlewareEngine is removed and metadata.duration is now optional.
Packaged-build safe: the CPU profile is written to the app logs directory (not process.cwd()) and a failed write can never break boot. Thresholds live in SLOW_THRESHOLD_MS; usage in docs/guides/diagnostics.md.
Also demote per-service stop/destroy logs to debug to quiet shutdown output.
LoggerService is a preboot singleton already consumed by
core/preboot/ modules, so its previous home in src/main/services/
violated the core→services layering. src/main/core/README.md
already lists "Logging infrastructure" as belonging in core/; this
commit makes reality match the docs.
The relocation also exposed 13 files that bypassed the @logger
alias with relative paths or @main/services/LoggerService directly.
Normalize them to the canonical @logger form so any future move
only requires an alias update, not call-site changes.
Only the main-process @logger alias (tsconfig.node.json and the
main section of electron.vite.config.ts) is retargeted. The
renderer-side LoggerService and its separate renderer alias are
untouched.
Signed-off-by: fullex <0xfullex@gmail.com>
Introduce a new `src/main/core/preboot/` subsystem that owns the
synchronous setup happening before `application.bootstrap()` runs. This
is the follow-up promised by c17dd033f to rewire v1's `initAppDataDir()`
to read from BootConfig instead of `~/.cherrystudio/config/config.json`.
## Startup phase vocabulary
The v2 main process now has three clearly-named startup phases, with the
definitions written into `src/main/core/README.md` as the authoritative
source:
- preboot — synchronous setup before the lifecycle container is
built. BootConfig load, userData resolution, top-level
Electron APIs. No DI, no services. Owned by core/preboot/.
- bootstrap — the `application.bootstrap()` orchestration function.
Freezes the path registry, runs Background/BeforeReady/
WhenReady stages. NestJS/Spring-style term, the only
meaning of "bootstrap" in v2 from this PR onward.
- running — steady state after bootstrap() returns.
v1's `src/main/bootstrap.ts` used "bootstrap" in the OS "early-boot
loader" sense, which clashes with the NestJS-style meaning v2's lifecycle
code has been using. "preboot" disambiguates without touching v2's
bootstrap terminology.
## userData location resolution
`resolveUserDataLocation()` is a single entry point that:
1. Applies any pending relocation (see below).
2. Reads `BootConfig.app.user_data_path[exe]` and calls setPath if
valid.
3. Falls back to portable-mode `<PORTABLE_EXECUTABLE_DIR>/data`.
4. Otherwise leaves Electron's default userData.
Step 2 mirrors v1's `getAppDataPathFromConfig()` path-validity check
(existsSync + accessSync W_OK). Portable fallback mirrors v1's branch
character-for-character. Only the data source changes: BootConfig, not
v1 config.json. v1→v2 data migration is handled separately by
BootConfigMigrator (from c17dd033f).
## Pending relocation (new)
A new BootConfig field `temp.user_data_relocation` under a new top-level
`temp.*` namespace holds in-flight userData relocation state:
{ status: 'pending', from, to }
{ status: 'failed', from, to, error, failedAt }
null
The `temp.*` namespace is reserved for ephemeral runtime state that
should never be backed up or synced (restoring a stale temp.* entry
could re-execute a relocation that already happened).
When a pending request is present, `executePendingRelocation()`
synchronously copies the entire `from` tree to `to` with
`fs.cpSync({ recursive: true, force: true, verbatimSymlinks: true })`,
then atomically commits the new location to BootConfig
(`app.user_data_path[exe] = to` + clear `temp.user_data_relocation` +
flush via temp file + rename).
Pre-flight checks fail fast before any bytes are copied:
- from !== to
- to is not inside from (with path.sep guard against sibling-prefix
false positives like /a vs /ab)
- from exists
- path.dirname(to) exists and is writable
On failure, the error is recorded as a `failed` state in BootConfig and
the function returns normally, letting Step 2 fall through to the
existing user_data_path. The `failed` record is the entry point for a
future renderer recovery UI (retry/abandon/investigate); this PR does
not implement the UI.
`failed` states are intentionally NOT auto-retried — that would turn a
one-off environmental failure into an infinite loop.
## v1 two-phase copy abandoned
v1's `copyOccupiedDirsInMainProcess()` worked around Windows file locks
on `occupiedDirs` (logs, Network, Partitions/webview/Network) by
splitting the copy into two phases (renderer copies the bulk, main
copies the locked dirs during the narrow "no renderer yet" window).
v2 abandons that distinction entirely: the previous process has fully
exited before preboot runs, so nothing is locked, and the entire
userData tree is copied as a single opaque unit. `occupiedDirs` has no
meaning in v2 and is deprecated in `packages/shared/config/constant.ts`
— it is kept on disk only because two v1-era call sites (bootstrap.ts
and BasicDataSettings.tsx) still reference it during the transition.
## Deprecations (code unchanged)
Two v1 files have their file-header @deprecated comments strengthened
but their code bodies untouched:
- src/main/bootstrap.ts — no longer imported anywhere; the file stays
on disk as reference for the follow-up cleanup PR.
- src/main/utils/init.ts — initAppDataDir is dead code;
updateAppDataConfig is still called by the v1 IPC handler at
src/main/ipc.ts:300 (out of scope for this PR).
## Known limitation (pre-existing)
LoggerService opens its winston file transport at module-load time
pointing at `app.getPath('logs')`, which on Windows/Linux resolves to
`<userData>/logs`. This happens BEFORE preboot runs, so the winston
file handle is bound to the pre-setPath path. After preboot calls
setPath('userData', <new>), winston keeps writing to the old handle.
Post-relocation logs therefore appear at the old location until the
next restart. This is a pre-existing v2 issue (LoggerService doesn't
respond to setPath) and is documented in executePendingRelocation's
doc comment; a follow-up should either lazy-open winston transports
or teach LoggerService to rotate on userData change.
## Out of scope (follow-up cleanup PR)
- App_SetAppDataPath IPC handler migration to write BootConfig
pending_relocation instead of v1 argv protocol
- BasicDataSettings.tsx renderer flow migration
- src/main/bootstrap.ts and src/main/utils/init.ts deletion
- occupiedDirs constant deletion
- LoggerService setPath responsiveness
## Tests
25 unit tests in userDataLocation.test.ts covering:
- getNormalizedExecutablePath (5 platform branches)
- resolveUserDataLocation normal resolution (9 scenarios including
AppImage/portable normalized key matching)
- resolveUserDataLocation pending relocation (11 scenarios including
pre-flight failure cases and the sibling-prefix regression guard)
Signed-off-by: fullex <0xfullex@gmail.com>
Move detailed lifecycle and application documentation from source-adjacent
READMEs to docs/en/references/lifecycle/, following the same organizational
pattern as docs/en/references/data/. Source-adjacent READMEs are reduced to
lean pointers. Also add main process application.get() examples to data docs.
Signed-off-by: fullex <0xfullex@gmail.com>