Pagination docs were scattered across api-types.md (types + cursor
semantics), data-api-in-renderer.md (hooks), data-api-in-main.md (offset
example + keyset note), api-design-guidelines.md (query params), and
data-ordering-guide.md (cache shapes + determinism), with no single
discoverable home for the offset-vs-cursor model.
Add docs/references/data/data-pagination-guide.md as the canonical hub
(mirrors data-ordering-guide.md): two modes, four-layer quickstart, wire
contract, server impl (offset + keyset cursor + multi-band caveat),
renderer consumption, FTS pagination, gotchas, and a see-also map. Other
docs keep their authoritative slice and link to the guide; the migrated
conceptual prose is removed from api-types.md to avoid duplication.
Also fix two pre-existing broken anchors found while verifying links
(database-patterns withWriteTx; ordering guide section number).
Consolidate the per-service <key>:<id> cursor codec and the keyset
WHERE/ORDER BY tuple into services/utils/keysetCursor.ts. keysetOrdering
derives the WHERE predicate and the matching ORDER BY from one direction
spec, so the two cannot drift apart and silently skip/repeat rows.
Migrate TranslateHistory, AgentSession, AgentSessionMessage (list), and
Painting to the shared util; delegate ftsSearch's codec to parseCursor /
encodeCursor while keeping its 422-throw policy. Harden Painting's cursor
from a single key to a defensive (orderKey, id) tuple.
Startup recovery (runStartupRecovery) runs ~60s after boot and reset every running row to pending (retry/singleton) or cancelled it (abandon/cancelRequested) without checking whether the row was a job the current process was still executing. A job enqueued during the 60s quiet window and still running when the sweep fired was therefore reset and re-dispatched — running its handler twice for one enqueue — or cancelled mid-flight. All recovery:'retry' handlers (file-processing, knowledge, image-generation, agent.task) are affected; #16125 (Translate OCR via File Processing) made it frequently reproducible.
Source fix: thread an isJobInFlight predicate (backed by JobManager.inFlightExecuted) into runStartupRecovery and filter in-flight rows out at the top of the per-type loop, above every strategy and the cancelRequested override. cancel() already owns in-flight cancellation, so excluding these rows from all branches is safe.
Defense-in-depth guard: spawnExecute now skips (warn) a jobId already present in inFlightExecuted — an idempotency invariant against any future re-dispatch path double-running a job. It does not fail the job (the original execution still finalizes it once), hence warn not error.
Tests: focused runStartupRecovery unit cases (retry / cancelRequested / singleton / abandon in-flight exclusion), an end-to-end no-double-dispatch integration test, and a spawnExecute idempotency smoke test. Job-and-scheduler docs updated.
main-process-architecture.md §3: state the no-renderer-imports rule (src/main + src/preload must not import renderer code; cross-process types → @shared, main-only → src/main), now enforced by the ESLint no-restricted-imports rule; clarify that the existing 'no automated enforcement yet' note refers to the internal direction edges. §7: track the lone remaining exception — main/utils/language.ts's relative renderer i18n import, deferred to the i18n migration PR.
renderer-architecture.md §7: add the anti-pattern of importing a shared bucket root (@renderer/utils, @renderer/types) instead of the specific file/topic, or giving types/ or utils/ a re-export root index.ts.
Mirror Shared Layer Architecture §3.1 onto the renderer top-level type buckets: types/ and utils/ are categories, not modules, so they carry no root index.ts. Consumers import @renderer/<bucket>/<topic>; a multi-file topic exposes a single curated index.ts with named exports and no wildcard re-export (export *).
Record the current deviations as §8 migration items: the types/ and utils/ root barrels, and the redundant @types alias (dissolve in favor of @renderer/types/<topic>).
Add main-process-architecture.md — the main-process peer of the renderer and shared-layer docs: a charter per top-level directory, placement rules, dependency direction, governance, and current deviations.
Refocus architecture-overview.md on the cross-process picture: refresh the process model and data flow to v2 (drop Redux), slim main-only sections to pointers, and replace the duplicated subsystem table with a reference map.
Cross-reference the new doc from naming-conventions §4.8 (per-root closed top-level) and §4.10 (feature vs type-bucket placement).
Remove the unused `LoaderReturn` from `@shared/types/codeTools.ts` (no consumers on main or the feat/chat-page truth branch). Its `status` field was the sole reason `@shared` imported `ProcessingStatus` from `@types` (src/renderer/types) — a Layer-4 layering violation now eliminated.
Record the remaining types/utils single-process residue (searchSnippet, pdf, EXTERNAL_APPS) and the error/serializable duplication cluster as a migration backlog in shared-layer-architecture.md; keywordSearch and SerializableSchema were verified cross-process via feat/chat-page and correctly stay.
`.ts` files under `src/shared` must use camelCase (naming-conventions §3.2);
kebab-case is only sanctioned under `packages/ui/` and `src/renderer/routes/`.
The `presets/` kebab naming came from best-practice-layered-preset-pattern.md,
which predated and conflicted with the authoritative spec.
- Rename presets/{code-cli,default-assistant,file-processing,mini-apps,
translate-languages,web-search-providers}.ts and utils/code-languages.ts
(plus the two matching __tests__ files) to camelCase, and update all importers
- Fix the upstream generator scripts/update-languages.ts to emit
codeLanguages.ts; otherwise `pnpm update:languages` would recreate the
kebab-named file
- Correct best-practice-layered-preset-pattern.md (kebab -> camelCase) and link
it to naming-conventions §3.2 so it cannot drift again
- Fix two stale `types/file` path references in file/architecture.md
Dissolve the by-kind @shared/config junk drawer per shared-layer governance: route each member by shape and actual consumer process — cross-process slices into types//utils//ai/, single-process code back into main/renderer (Invariant 1.1). Confirm each item's real consumer process rather than trusting the directional plan (API_SERVER_DEFAULTS is renderer-only, MIN_WINDOW_* is cross-process, providers.ts is renderer-only), and drop dead consts (ZOOM_LEVELS/ZOOM_OPTIONS, bookExts, thirdPartyApplicationExts).
Purge runtime logic from types/ so the bucket holds only declarations: move serializeError + AI-SDK error guards to utils/error.ts, the FileHandle factories/guards to utils/file/handle.ts, isSerializable + SerializableSchema to utils/serializable.ts, and the tab-instance guard/normalizer to utils/tabInstanceMetadata.ts. Tests follow the logic to utils/__tests__; the type-level ipc contract test is retired (its invariants kept as a breadcrumb for the future IpcApi Zod schema). types/ is now logic-free and test-free.
Also: remove the dead @shared mock from the packages/ui code-editor test so packages/ui no longer references production code; fix the data-classify preference generator prompts import and the update-languages output path to the relocated modules.
Update shared-layer-architecture (3.1 type/util test rule, 5/6 config dissolution) and renderer-architecture cross-references.
Move the four ad-hoc top-level @shared dirs into the closed top-level set, routed by shape: pure logic + class blueprints to utils/, type/contract declarations to types/.
command -> utils/command + types/command; file -> utils/file + types/file; shortcuts -> utils/shortcut + types/shortcut; externalApp -> utils/externalApp + types/externalApp.
Replace the exported menuRegistry singleton with a pure resolveMenu over a frozen contribution set (Invariant 1.2: no exported instance); keep MenuRegistry as a per-process blueprint sharing the same resolve algorithm.
Rewrite all consumer imports per symbol origin and align shared-layer-architecture and renderer-architecture docs.
Add docs/references/shared-layer-architecture.md as the authoritative reference for src/shared: two invariants (cross-process; no mutable runtime state), the closed top-level set {ai, data, ipc, types, utils} by category, types/utils shape rules (single-file vs subdirectory, barrels, constants guardrail), the placement decision, anti-patterns, and a deferred migration section (config dissolution with constant.ts itemized).
Relocate @shared-internal rules out of renderer-architecture.md and architecture-overview.md into the new doc (leaving pointers); repoint command's cross-process cell to @shared/utils/command + @shared/types/command in renderer-architecture sections 6 and 8; link the per-root applications of the closed-top-level rule from naming-conventions section 4.8.
- Emphasize cross-process as the entry gate for @shared; single-process logic stays in its own layer (plain shared types excepted). Mirror as a summary in architecture-overview.
- Refine the command-capability example to decompose by shape across @shared/command, utils/command, hooks/command, and components/command; update the §8 alignment/coupling notes for the resolved component/hook → feature edges.
Add docs/references/renderer-architecture.md as the canonical reference for src/renderer organization: the two-axis model (type x domain), four-layer downward dependency direction, per-directory responsibilities, the features/ definition, curated-barrel public API with import/no-restricted-paths enforcement, closed top-level governance, anti-patterns, a target-vs-current-state section, and industry references.
Align the existing docs: collapse the renderer subtree in architecture-overview.md to a pointer, and in naming-conventions.md list renderer services/ in the type-bucket set and link features/ placement to the new doc.
Add `src/shared/ipc/schemas/**/*.ts` to the `data-schema-key/valid-key`
rule's files glob so route/event keys for every current and future IPC
domain are enforced automatically, replacing the hard-coded file list that
silently missed each migrated domain (selection, window, knowledge,
fileProcessing, webSearch). Add a guard that skips zod data-field names —
keys inside a `z.*(...)` object literal — so only the route/event strings
are constrained while `Object.freeze(...)` registries stay validated.
Drop the stale "global registry" type assertions in schema.types.test.ts
that manually enumerated every migrated route/event union; that positive
contract is already proven at compile time by the production handler maps,
leaving the @ts-expect-error negative assertions as the test's real value.
Remove an unused eslint-disable directive surfaced by the linter.
Add docs/references/data/database-construction.md as the single home for how the SQLite DB is built and evolved: boot init order, drizzle migrations (regenerate-never-rename, CI gates, additive-vs-rebuild), the CUSTOM_SQL_STATEMENTS every-boot replay (~0.1ms O(1)), and the FTS5 fts_rowid rowid-stability rule, plus a gotchas table. Move the Migrations and Custom SQL sections out of database-patterns.md into it (left as pointers), and index it from data/README.md and src/main/data/db/README.md.
Fix stale references found while consolidating: wrong generate command, customSql.ts vs customSqls.ts, columnHelpers.ts vs _columnHelpers.ts, a nonexistent messageFts.ts, yarn vs pnpm, the v2-todo single-0000 claim, the generated-column wording, and v1 data.blocks vocabulary in the testing doc.
Replace the `await import(...)` cycle-breaking hack with a lightweight, type-safe service locator. Participating services self-register and resolve siblings lazily through `getDataService(...)`; the registry imports services only as `import type`, so it stays a sink in the import graph and no value cycle can form. Only the services in a real cycle join the registry.
- add dataServiceRegistry (register/get) + symmetric migration of the two cycles: Message<->Topic, Provider<->ProviderRegistry
- tests load the sibling via a side-effect import so it self-registers
- document the pattern in data-api-in-main.md and a new services/README.md
Make the IpcApi docs explicit about three things surfaced in review:
- overview: Why Narrow the Surface (before/after + the type/cheat-sheet/audit wins), Direction Cheat Sheet (IpcRoute vs IpcEventName + out-of-scope), and No One-Way R->M Primitive (every R->M is invoke/handle; void still round-trips).
- migration guide: Escape Hatch criteria (direction gate then frequency) with Tab_MoveWindow as the sole qualifying carve-out, its two hard conditions (still gated, still documented), and scope discipline for the sibling one-off channels; add Tab_MoveWindow to the Not In Scope table.
- README + usage: navigation pointers and the R->M / M->R high-frequency asymmetry (M->R stays via class B, R->M may escape).
> ### 🚨 Branch strategy — read before opening this PR
>
> The v2 refactor has merged into `main`, so **`main` is the default
branch for active development** (v1 and v2 code currently coexist there
— expect large, breaking changes).
>
> - **Active development** (features, refactors, optimizations, fixes
for the current codebase) → target **`main`** (the default base).
> - **v1 maintenance** (hotfixes and subsequent v1 releases) → branch
from and target **`v1`**, _not_ `main`.
>
> A v1 fix does **not** auto-carry to `main`: if the same bug exists on
`main`, open a separate forward-port PR targeting `main`. Before
touching subsystems being replaced, read `docs/references/data/` and
watch for `@deprecated` markers — they flag code being deleted.
### What this PR does
Before this PR:
Development builds always used the fixed `Dev` suffix for Electron's
default `userData` path, so parallel dev instances shared the same app
data path and single-instance lock.
After this PR:
Development builds can set `CS_DEV_USER_DATA_SUFFIX` to use a unique
`userData` suffix per dev instance. Blank values fall back to `Dev`. The
development guide, preboot reference, and `.env.example` document both
`.env` and inline command usage. `pnpm debug` also loads `.env` through
`dotenv`, matching `pnpm dev` while preserving Electron argument
passthrough with `dotenv -- electron-vite -- ...`.
<!-- (optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)`
format, will close the issue(s) when PR gets merged)*: -->
Fixes # N/A
### Why we need it and why it was done in this way
The following tradeoffs were made:
This keeps the change scoped to the existing preboot dev `userData`
suffix path instead of introducing a new config system. The suffix is
trimmed and empty values fall back to the current default to avoid
accidentally running dev against the unsuffixed packaged data path.
The following alternatives were considered:
Adding a dedicated package script for each development instance was not
chosen because instance names are local developer concerns. A single
environment variable is enough and works from either `.env` or one-off
shell commands.
Links to places where the discussion took place: N/A
### Breaking changes
N/A
### Special notes for your reviewer
This is dev-only behavior. Packaged builds still use the existing
BootConfig-driven userData resolution path.
### Checklist
This checklist is not enforcing, but it's a reminder of items that could
be relevant to every PR.
Approvers are expected to review this list.
- [x] Branch: This PR targets the correct branch — `main` for active
development, `v1` for v1 maintenance fixes
- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: [Write code that humans can
understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans)
and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle)
- [ ] Refactor: You have [left the code cleaner than you found it (Boy
Scout
Rule)](https://learning.oreilly.com/library/view/97-things-every/9780596809515/ch08.html)
- [x] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [ ] Documentation: A [user-guide update](https://docs.cherry-ai.com)
was considered and is present (link) or not required. Check this only
when the PR introduces or changes a user-facing feature or behavior.
- [x] Self-review: I have reviewed my own code (e.g., via
[`/gh-pr-review`](/.claude/skills/gh-pr-review/SKILL.md), `gh pr diff`,
or GitHub UI) before requesting review from others
### Release note
<!-- Write your release note:
1. Enter your extended release note in the below block. If the PR
requires additional action from users switching to the new release,
include the string "action required".
2. If no release note is required, just write "NONE".
3. Only include user-facing changes (new features, bug fixes visible to
users, UI changes, behavior changes). For CI, maintenance, internal
refactoring, build tooling, or other non-user-facing work, write "NONE".
-->
```release-note
NONE
```
---------
Signed-off-by: kangfenmao <kangfenmao@qq.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
The per-domain schemas/__tests__ (window + selection) asserted zod
primitives: that z.boolean() rejects a string, that z.infer yields boolean,
that never-parsed output schemas round-trip — i.e. they tested zod, not our
code. The route inventory they locked is already enforced at compile time by
IpcHandlersFor exhaustiveness plus the global IpcRoute union test.
Remove all four. Document the boundary across the IPC docs (usage /
schema-guide / migration-guide): test the handler (real behavior), not the
schema; business validation lives in the handler/service, so a schema worth
unit-testing effectively never arises.
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.
First real domain migration onto IpcApi. Collects the selection feature's
6 request channels and 2 main->renderer events off their dedicated
IpcChannel entries and the hand-written preload bridge onto the unified
framework.
- shared/ipc/schemas/selection.ts: request zod schemas + event types;
actionItem bound to SelectionActionItem via z.ZodType for definition-site
schema/type sync
- main/ipc/handlers/selection.ts: thin adapters delegating to
SelectionService; pin_action_window routes straight to
wm.behavior.setAlwaysOnTop(ctx.senderId), dropping the id->window->id
round-trip
- SelectionService: remove registerIpcHandlers and pinActionWindow; emit
the toolbar events via directed IpcApiService.send(toolbarWindowId, ...)
- renderer (toolbar/action/settings): ipcApi.request(...) for calls,
useIpcOn(...) for events
- remove window.api.selection from preload and the Selection_* IpcChannel
enum entries
Behavior-preserving (independently verified): the only delta is
pin_action_window dropping the unreachable untracked-window fallback, now a
benign no-op when ctx.senderId is null. Documents the z.ZodType mirroring
convention in ipc-migration-guide.md.
Add executionPolicy ('run-on-change' default | 'bootstrap-only') to ISeeder
and a seedRunner:bootstrapCompleted marker written after the first fully-
successful seeding pass. SeedRunner skips bootstrap-only seeders once the
window closes, replacing DefaultAssistantSeeder's hand-rolled seed-journal
guard; SEED_KEY_PREFIX returns to a private constant and the cross-seeder
name export is removed.
Resolve the spec conflict between api-design-guidelines.md (orderBy +
order) and api-types.md / SortParams (sortBy + sortOrder) by adopting
sortBy + sortOrder everywhere:
- api-design-guidelines.md: align the sorting convention with
SortParams and cross-link api-types.md
- ListOptions: extend SortParams; rename the direction field
orderBy -> sortOrder
- AgentService + tests: follow the ListOptions rename
- renderer types/agent.ts: drop the unused, drifted ListOptions copy
### What this PR does
Before this PR:
- Knowledge embeddings and reranking ran through the legacy
embedjs-based
knowledgeV1 stack with their own provider clients, independent of the
app's
AI service.
- File-processing intake accepted several heterogeneous input shapes,
and
knowledge file items were tracked by FileEntry ids, coupling file
content to
the file-manager entry/cache.
After this PR:
- Embeddings and reranking are routed through the unified `AiService`
(with
cherryin rerank support) and guarded by strict embedding-dimension
validation
that rejects stale/mismatched vectors.
- File-processing intake is collapsed to a single path-based model;
knowledge
file items are stored by base-relative path under the knowledge-base
directory, and v1 uploads are copied into the v2 base dir during
migration so
migrated items stay reindexable/restorable.
- Legacy `knowledgeV1` is removed; the orchestration services were
renamed to
`KnowledgeService` / `FileProcessingService`.
- Chat -> knowledge attach is temporarily disconnected (tracked TODO)
while the
v2 file-manager bridge is rebuilt.
Fixes #N/A (no linked issue)
### Why we need it and why it was done in this way
Routing embeddings/rerank through `AiService` unifies provider handling
and
credentials and removes the parallel embedjs client stack and its v1
coupling.
Storing knowledge files by base-relative path (instead of FileEntry ids)
makes
each knowledge base self-contained and portable.
The following tradeoffs were made:
- A large, coordinated refactor plus a migration step that physically
copies v1
uploads into the v2 base dir, in exchange for removing the parallel
client
stack and making bases self-contained.
- Base-relative path storage required a fail-fast/dedup strategy for
same-named
files and a guard for blank legacy filenames.
The following alternatives were considered:
- Keeping the embedjs stack behind an adapter — rejected; perpetuates
the
parallel client and v1 coupling.
- Keeping FileEntry-id storage — rejected; couples knowledge files to
the
file-manager cache and blocks portability.
### Breaking changes
- `knowledgeV1` is removed. Legacy v1 knowledge data reaches v2 only
through the
v2 migrators; there is no v1 fallback.
- The v2 knowledge HTTP API (API gateway) now returns v2-native
per-entry fields
(`embeddingModelId`, `createdAt` on base entries; `chunkId`,
`scoreKind`,
`rank` on search results). The response envelope (`knowledge_bases`,
`searched_bases`, `total`) is unchanged. See
`v2-refactor-temp/docs/breaking-changes/2026-06-05-knowledge-api-v2.md`.
### Special notes for your reviewer
- This branch went through several rounds of multi-agent code review.
The most
recent 6 commits address review findings: directory-import path
collisions,
migrated-file source copying + blank `relativePath` guard, addItems
rollback
error preservation, eager `document_to_markdown` output-target
validation, a
`CompletedKnowledgeBase` type guard, and breaking-changes doc
corrections.
- Chat -> knowledge attach is intentionally disconnected for now
(tracked in
`v2-refactor-temp/docs/knowledge/knowledge-todo.md`).
- Local full `pnpm lint`/`pnpm test` was not run per the project's
review
conventions; please rely on CI / `pnpm build:check`.
### Checklist
- [x] Branch: This PR targets the correct branch — `main` for active
development, `v1` for v1 maintenance fixes
- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: Write code that humans can understand and Keep it simple
- [x] Refactor: You have left the code cleaner than you found it (Boy
Scout Rule)
- [x] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [ ] Documentation: A user-guide update was considered and is present
(link) or not required.
- [x] Self-review: I have reviewed my own code before requesting review
from others
### Release note
```release-note
NONE
```
---------
Signed-off-by: eeee0717 <chentao020717Work@outlook.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The features/ vs type-bucket split applies to both process roots (src/main/features/, src/renderer/features/); update the definition and the sibling-bucket note to cover the renderer (components/, hooks/, utils/).
Rewrite the src/ tree to the current v2 layout: add main/{ai,features,utils}, drop the to-be-removed knowledge/integration/apiServer and the v1 renderer store/aiCore, remove the redundant renderer src/ level, and move shared/ under src/.
Replace the flat subsystem table with a core/ subsystem table (Lifecycle, Window Manager, Scheduler & Jobs, Paths), link each system in Four Data Systems to its overview, and drop the non-core MCP/CherryClaw/Knowledge/Message-System/API-Server rows. Correct window ownership to WindowManager, and point to Naming Conventions 4.10 for the feature-vs-bucket placement rule.
Establish when a main-process domain belongs in src/main/features/ versus the services/ and utils/ type-buckets: a domain earns its own features/<domain>/ tree only when it is large, complex, and multi-file, while a single cohesive service (even domain-specific) stays as services/<Domain>Service.ts with its lone helper in utils/.
Decouple naming from placement in §4.5, and surface the rule in the §1 quick reference and the §7 decision tree.
DataApi handlers and their services may perform only SQLite reads/writes via Drizzle; fs/network/process/external-service side effects are prohibited regardless of nesting depth or an accompanying DB write. Add a Hard Rule section to api-design-guidelines, scope-limit service domain workflows to DB I/O, and echo the boundary in the overview and README.
Previously the CSLOGGER_* console overrides and the verbose (silly) file level were gated behind isDev, so a packaged build could not raise its log level for troubleshooting. Replace the isDev gates in both the main and renderer LoggerService with isDev || DIAGNOSTICS_ENABLED, so setting CS_DIAGNOSTICS makes the logger behave exactly as in dev: verbose file level, console output, and the CSLOGGER_* filters all turn on together. Idempotent in dev.
Reuses the existing DIAGNOSTICS_ENABLED flag as the single source of truth; the renderer reads CS_DIAGNOSTICS via the preload-exposed process.env. Document the behavior in the diagnostics and logging guides.
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.
Flip getWindowsByType to return live BrowserWindow[] (skipping destroyed) and
add getWindowInfosByType for the serializable WindowInfo[] snapshot, so the
"Info-suffixed = snapshot / unsuffixed = instance" convention holds on both the
by-id and by-type axes. Remove getAllWindows(): ManagedWindow[], whose only
production consumer was an AppMenuService workaround reaching into the internal
record to grab .window.
Migrate all callers off the deleted/old shapes:
- AppMenuService zoom and SettingsWindowService/SubWindowService main-window
lookups use getWindowsByType(Main) directly; the two-step getWindow(id)
collapses to one call.
- SubWindowService sub-window membership check uses getWindowInfosByType, which
still carries the WindowManager UUID it compares against.
- navigate.ts deep-link resolves via getWindowsByType(Main)[0].
- mcpInstall.ts switches to broadcastToType(Main, ...) since it was a pure
renderer IPC send.
With those two callers gone, delete the deprecated MainWindowService.getMainWindow().
Update WindowManager unit tests, the affected service mocks, and the
window-manager API reference table.
Prevent reading the IPC-handler table as a reason to promote an IPC-only class into lifecycle:
- Add a "placement, not promotion" note before the table, scoping it to services that are already lifecycle and clarifying row 3 ("belongs to the domain") only decides handler placement.
- Tighten the stateless-handler note: a class whose only job is registering stateless IPC is not a lifecycle service.
- Add Common Mistakes #8 ("Lifecycle service as an IPC bucket").