The resources/database/drizzle SQL files and meta snapshots are v1-era CherryClaw agents-db migrations with no runtime consumer — the active v2 migrations live in migrations/sqlite-drizzle and are loaded from app.database.migrations. Delete the directory and clean up the now-dangling references: three source comments in AgentsDbMappings.ts that cited the deleted SQL files as the v1 column-type source (the epoch-ms notes are kept inline), and a stale doc row in cherryclaw/scheduler.md pointing to a migration file that no longer exists.
The setter updater contract previously said "must be pure" only in the sense of "don't mutate prev / return a new value" (the isEqual short-circuit footgun); it did not cover side effects inside the updater.
Document that updaters must also be side-effect-free: don't smuggle a derived value out (e.g. into an enclosing-scope variable) to drive post-write work, and don't rely on how often or when the updater runs. To react to what changed, derive it from the value transition in a useEffect that watches the value.
Deliberately does not promise single synchronous invocation, to keep the setter free to batch/retry/defer later. Updates the CacheSetStateAction type doc, the useCache @remarks (canonical reference for all three hooks), and cache-usage.md.
useCache, useSharedCache and usePersistCache setters now accept a React-style functional updater `(prev) => next` in addition to a concrete value. The updater resolves `prev` from the latest stored value at write time rather than the render-time snapshot, making read-modify-write correct across an `await` — the root cause of the keep-alive overwrite race behind #16460.
`prev` is typed shallow-readonly (`ReadonlyValue<T>`), so mutating it in place and returning the same reference — which the CacheService `isEqual` short-circuit would otherwise swallow silently — is a compile error. Concrete-value calls are unchanged, so existing consumers keep compiling.
The renderer useCache mock mirrors the functional branch with the same default fallback; docs and hook tests updated. Consumer call-site adoption lands separately.
Move the renderer-side AI-streaming runtime (IpcChatTransport,
TopicStreamSubscription, streamDispatchCoordinator) out of the top-level
src/renderer/transport/ directory into the shared services/aiTransport/
bucket. By shape these are stateful runtime singletons/classes, and the
runtime is cross-surface (consumed by chat, quick-assistant, selection),
so per the renderer architecture it routes into services/, not its own
top-level directory.
- Add a curated index.ts barrel exposing only the externally consumed
symbols (ipcChatTransport, TopicStreamSubscription, ExecutionTerminal);
the class, dispatch coordinator and helpers stay private.
- Update the 6 consumer sites (5 imports + 1 vi.mock) to the barrel.
- Sync architecture and AI docs to the new path; drop the now-resolved
transport/ deviation from the renderer-architecture pending table.
- Slim @shared/types/error.ts to the cross-process SerializedError shape
- Relocate serializeError (+ toSerializable) to src/main/ai/utils/serializeError.ts;
re-point the 4 main/ai consumers
- Delete the @shared-side dead AI-SDK subtype family + 23 isSerialized* guards
(utils/error.ts) and the unused ProviderSpecificError class
(renderer keeps its own live parallel copy)
- Move the serializeError test to main/ai/utils; assert discriminant fields directly
instead of via the deleted guards
- Doc: mark the section 6 error/serializable row resolved
Eliminate src/renderer/config entirely, completing the §8 dissolution
started in 1065cd4dfd. The two remaining files held a mix of genuinely
app-global constants, domain-owned constants, and v1 dead code.
- Add utils/platform.ts for the platform predicates (platform/isMac/
isWin/isLinux/isDev/isProd) and repoint ~60 cross-domain consumers.
- Relocate domain constants to their owners: LONG_TEXT_PASTE_THRESHOLD
-> composer/composerPaste; knowledge defaults -> knowledge/rag;
THEME_COLOR_PRESETS + defaultByPassRules -> CommonSettings;
MAX_COLLAPSED_CODE_HEIGHT -> CodeBlockView/constants;
API_SERVER_DEFAULTS -> ApiGatewaySettings; SiliconFlow/PPIO client ids
+ TOKENFLUX_HOST -> utils/oauth (kept below the consuming pages layer);
occupiedDirs -> BasicDataSettings (still @deprecated v1).
- Dissolve env.ts: inline the APP_NAME literal at AboutSettings and
import the avatar/logo PNGs directly at each consumer; drop the now
obsolete config/env test mocks (Vite resolves the assets natively).
- Delete v1 dead constants with no consumers (DEFAULT_TEMPERATURE,
DEFAULT_CONTEXTCOUNT, SYSTEM_PROMPT_THRESHOLD, message-count caps,
context caps, DEFAULT_STREAM_OPTIONS_INCLUDE_USAGE).
- Update renderer-architecture (§3/target layout/§8) and the preboot
doc/comment references that pointed at the old occupiedDirs location.
src/renderer/hooks/agents/ is a feature namespace grouping the agent
feature's hooks, not a collection of agents. Per naming conventions
§4.9 it should be singular, matching sibling namespaces hooks/chat/,
hooks/tab/, hooks/translate/.
- git mv src/renderer/hooks/agents -> src/renderer/hooks/agent
- update 62 import paths across 33 files
- clarify §4.9 so the plural `agents/` example is read by role
(collection bucket vs feature namespace), not by the word
Migrate window-bounds persistence off the electron-window-state library into
a WindowManager built-in `rememberBounds` capability, backed by the main-process
persist cache (`window.bounds` key — its first real consumer).
- New `windowBoundsTracker` free-function module: validates the stored record
(including displayBounds), restores onto the display the window was last on
(clamping into its work area, never resetting to primary), and snapshots at
teardown via getNormalBounds + isMaximized.
- Singleton-only gate (dev warning for non-singleton types). Runtime toggle
`wm.setRememberBounds` (orthogonal to the registry flag; OFF drops only that
type's slot) plus `wm.peekWindowBounds`.
- Persist at three teardown exits: native close (singletons), before
window.destroy() in destroyWindow (programmatic destroys), and a new onStop
so shutdown writes land before CacheService flushes its persist map.
- Wire Main + QuickAssistant. Main re-applies maximize consumer-side on its own
show schedule (tray-on-launch defers to first show); remove electron-window-state
and its orphaned keepers/constants/comments.
Fullscreen is not persisted and old *-state.json is not migrated (one-time
reset, loseable). Adds tracker/integration/persist tests, extends the main
CacheService mock with persist methods, and documents the capability plus a
breaking-change note.
State the previously-implicit rules that produced "features is an isolated
island" misreadings, then tighten the result for concision:
- Make explicit that the app layer (windows/routes/pages) is a feature's
only legal consumer (window/page -> feature are legal downward edges); a
feature is isolated only horizontally, from sibling features.
- Add the same-kind-peering vs same-slice-isolation distinction
(component -> component allowed; feature -> feature not) and note that a
feature-internal piece follows identical type-axis rules as its top-level
counterpart.
- Add a 4.1 Promotion Rule with an operational trigger and a
chat -> features/chat worked example; fix the dangling 4.4 references.
- Add the DAG rationale for the two banned edges (shared -> feature,
feature -> feature).
- Fix small inconsistencies: pages/ may depend on primitives; shared-layer
order includes workers; disambiguate bare (4.8) -> (Naming Conventions
4.8); reclassify transport/ as a cross-surface ai runtime (not
chat-exclusive) bound for the shared services/ bucket.
Also clean up two dead command mocks targeting the non-existent
@renderer/features/command path: fix the stale path in TopicBranchPanel's
test so the mock intercepts the real @renderer/components/command import,
and drop the unused mock in GlobalSearchPanel's test.
Make the main-authoritative and renderer persist tiers express presence as
deviation from the schema default rather than backing-store membership, extend
the API symmetrically, and unify the debounced-write cadence.
- hasPersist now reports whether the effective value differs from the schema
default (main + renderer). loadPersist/loadPersistCache seed every key, so the
old Map-membership check was always true and carried no information.
- Add deletePersist (reset-to-default) on both tiers; delegates to setPersist so
it inherits the same-value no-op, subscriber notify, and renderer cross-window
broadcast.
- Add subscribePersistChange on the main tier for in-main consumers, mirroring
subscribeChange (main-local, never relayed to renderers). setPersist now
notifies persist subscribers on actual change.
- Align persist debounce to 350ms (main + renderer) and BootConfig save debounce
to 350ms for more coalescing of paused/bursty writes; extract the renderer
magic number into a named constant.
- Normalize CacheService comments to JSDoc on core methods and document the
default-relative persist semantics; update cache-overview.md.
Add an independent, main-authoritative persist tier to the main-process
CacheService, stored as a JSON file at {userData}/cache.json. It is inline
(mirroring the renderer persist structure), fixed-keys-only, with no delete
or TTL; values are loseable and fall back to schema defaults on miss.
Writes are debounced (200ms) and flushed atomically (temp file + rename),
with a flush on service stop. Unknown/stale keys in the file are pruned on
load to keep the fixed-keys contract. The renderer persist IPC relay is
untouched: Main cannot read renderer persist and vice versa.
This phase is architecture-only: the schema ships a single scaffold key
(internal.persist_probe) and no business consumer; window-state and other
consumers will follow. Docs under docs/references/data and CLAUDE.md are
updated to distinguish the new Main persist store from the relay.
The context/ directory was a by-kind bucket: React providers are
components, not their own layer. Dissolve it per renderer-architecture
section 8 by splitting each provider into its component (-> components/)
and its context object + accessor hooks (-> hooks/), mirroring the
existing command decomposition.
- ThemeProvider -> components/ThemeProvider.tsx + hooks/useTheme.ts
- CodeStyleProvider -> components/CodeStyleProvider.tsx + hooks/useCodeStyle.ts
- Tab behavior layer -> hooks/tab/ (useTabsContext, useCurrentTab,
useTabSelfMetadata, useTabs, index); TabsProvider/TabIdProvider ->
components/layout/
- Extract pure emoji-icon logic to utils/tabIcons.ts
- Remove dead TabsContextValue ops (hibernateTab/wakeTab/setTabs) and a
stale ThemeProvider comment
- Repoint ~90 import sites and test mocks; migrate provider/hook tests
- Update routes README and renderer-architecture section 8
With ImportService migrated to DataApi (#16415), the v1 Redux store at
src/renderer/store/ has zero runtime consumers. Delete the directory (30
files) along with the already-stubbed main-process ReduxService bridge.
Clean up the now-stale residue:
- Remove dead vi.mock('@renderer/store') factories from 14 test files
whose code under test no longer imports the store.
- Drop Redux from the CLAUDE.md "Removing" enumerations and refresh the
renderer-architecture / knowledge-service / v2-todo docs.
- Fix dangling store/ references in the MiniAppMigrator and
builtinMcpServers comments.
The v2 migration's Redux Persist readers (ReduxStateReader etc.) are left
untouched: they read serialized on-disk data, not this store.
Dissolve the renderer types/index.ts re-export barrel (Phase 2):
- Repoint 252 consumers to @renderer/types/<topic> (intra-types files use
relative ./topic); covers @renderer/types, @types, and the equivalent
'./', './index', '@renderer/types/index' specifier forms.
- Delete src/renderer/types/index.ts.
- Remove the @types alias from tsconfig.web.json, electron.vite.config.ts,
and the eslint main/preload boundary guard.
- Retire the resolved types-barrel and @types deviations from the renderer
and main architecture docs.
Move src/shared/ipc/errors.ts to src/shared/ipc/errors/index.ts so each migrated domain can host its own error-code map as a sibling errors/<domain>.ts — value-importable by both processes and zod-free. The @shared/ipc/errors barrel path is unchanged, so every importer and test resolves identically (typecheck + IPC tests green).
Document the IpcErrorCode usage convention that previously lived only in code comments: the framework-code single source of truth, the open (string & {}) tail for domain codes, where domain codes belong (errors/, not schemas/, since the renderer may only import type from schemas), and why they are imported directly rather than aggregated through a barrel.
### What this PR does
**Before this PR**, Cherry Studio managed external CLI binaries through
five uncoordinated mechanisms — each requiring its own download script,
IPC channel, and on-disk layout:
| Mechanism | Where it lived | How it worked |
|---|---|---|
| rtk extraction | `src/main/utils/rtk.ts` + `AgentBootstrapService` |
Copies bundled binary to `~/.cherrystudio/bin/` |
| OpenClaw installer | `resources/scripts/install-openclaw.js` |
Downloads from GitHub/mirror with custom extraction |
| CodeCliService | `src/main/services/CodeCliService.ts` | `bun install
-g` to `~/.cherrystudio/install/global/` |
| ripgrep | `node_modules/@anthropic-ai/claude-agent-sdk/vendor/` |
Vendored, hardcoded path in `FileStorage` |
| (old) MiseService | `src/main/services/MiseService.ts` | Bundled mise
+ `mise use -g` |
**After this PR**, a single `BinaryManager` lifecycle service owns all
third-party CLI binary acquisition. It wraps
[mise](https://mise.jdx.dev) as the only acquisition backend (no custom
`BinaryBackend` interface — mise's polyglot grammar already covers
`npm:`, `pipx:`, `github:`, registry entries). Tools install into an
isolated environment under `~/.cherrystudio/mise/` so user-level mise
installs are never touched. `uv`, `bun`, `rg`, and mise itself ship
bundled at build time for instant first-run availability; everything
else flows through mise on demand.
<img width=\"1304\" height=\"714\" alt=\"BinaryManager settings UI\"
src=\"https://github.com/user-attachments/assets/7a4b78ab-5aa2-4e97-9ab7-134b20a4d78d\"
/>
<img width=\"1165\" height=\"748\" alt=\"Three-state managed vs bundled
vs not-installed\"
src=\"https://github.com/user-attachments/assets/a0dcfb7d-8bc3-4acd-b563-0fc04d99e252\"
/>
<img width=\"523\" height=\"328\" alt=\"Custom tool dialog\"
src=\"https://github.com/user-attachments/assets/90c3ee95-7f2a-4daf-a334-f20de6ff5ca2\"
/>
Fixes#15183. Addresses #15370.
### Why we need it and why it was done in this way
Adding a new managed CLI tool should be a one-line preset entry — not 4+
files of bespoke download/extract/IPC code. mise is a mature polyglot
tool manager that already speaks the backends Cherry needs.
**Tradeoffs made:**
- **mise bundled at build time** (~15 MB per platform) rather than
downloaded at first run — faster first-run UX, no chicken-and-egg on a
fresh install.
- **Fully isolated mise environment** (\`HOME\`/\`XDG_*\`/\`MISE_*\` all
relocated under \`feature.binaries.data\`) — Cherry never reads or
writes the user's own \`~/.config/mise/\` or \`~/.local/share/mise/\`.
- **No custom \`BinaryBackend\` interface.** mise's grammar (\`npm:\`,
\`pipx:\`, \`github:\`, registry) is already polyglot; wrapping it would
be a shallow seam that re-implements what mise owns. Removing this
abstraction makes consumers simpler (deletion test passes).
- **Auth-token policy: opt-in only.** Ambient \`GITHUB_TOKEN\` /
\`GH_TOKEN\` are not forwarded into mise's process env. Users who hit
GitHub's unauthenticated 60 req/hr API limit can set
\`CHERRY_GITHUB_TOKEN\` to raise it to 5000 req/hr without consenting to
share their general shell token.
- **China mirror auto-detection.** \`isUserInChina()\` toggles
\`NPM_CONFIG_REGISTRY=registry.npmmirror.com\` +
\`PIP_INDEX_URL=pypi.tuna.tsinghua.edu.cn\` for every npm/pipx backend
transparently.
**Alternatives considered:**
- *Keep per-tool install scripts.* Doesn't scale — each new tool is 4+
files of duplicated logic.
- *Use mise from user's \`PATH\`.* Would depend on user having mise
installed and could conflict with their config.
- *Custom \`BinaryBackend\` abstraction.* Shallow wrapper over mise's
grammar; no second backend in sight; deletion test passes.
**Scope (what's in / what's out):**
- **In:** uv, bun, ripgrep, claude-code, openclaw, gh, opencode,
gemini-cli, lark, kimi-cli, qwen-code, iflow-cli, github-copilot-cli —
anything mise can install as a single relocatable binary.
- **Out:** \`OvmsManager\` (multi-file server provisioning, hardware
detection, generated config); Tesseract OCR data (not a binary; lives
with \`OvOcrService\`).
### Breaking changes
None at the user-facing layer. v2 data is throwaway per CLAUDE.md, so
the preference-key rename (\`feature.mise.*\` → \`feature.binaries.*\`)
intentionally ships without a migrator.
### Special notes for your reviewer
**Architecture & docs**
- \`docs/references/binary-manager/README.md\` — scope criterion,
persisted/contract surface, bundled-vs-mise state contract, China mirror
behavior, \`CHERRY_GITHUB_TOKEN\` opt-in, adding a new managed binary.
- \`CLAUDE.md\` adds a \`**MUST READ**\` link next to Lifecycle / Window
Manager / Data / Paths.
- The mise-shim-wins-over-\`cherry.bin\` precedence rule is documented
in the README's "State contract" section.
**Code orientation**
- \`src/main/services/BinaryManager.ts\` — the lifecycle service. Wraps
mise via \`runMise()\`; isolated env in \`buildIsolatedEnv()\`; bundled
extraction with atomic tmp+rename; per-tool try/catch so a single
failure can't abort init; \`isManagedBinaryReady()\` verifies the
resolved file is executable (not just that mise thinks it's installed).
- \`packages/shared/data/presets/binary-tools.ts\` —
\`PREDEFINED_BINARY_TOOLS\` registry; \`tool\` field is a mise spec.
-
\`src/renderer/src/pages/settings/McpSettings/EnvironmentDependencies.tsx\`
— three-state UI (\`managed\` / \`bundled\` / \`not-installed\`) backed
by \`Binary_GetState\` + \`Binary_ProbeBundled\`.
- \`scripts/download-binaries.js\` — build-time downloader for mise / uv
/ bun / rg (HTTPS + sha256-verified, archive-aware extraction).
**Migration**
- \`OpenClawService.install()\` is now two lines delegating to
\`BinaryManager\` — \`install-openclaw.js\` is gone.
- \`CodeCliService\` no longer uses \`bun install -g\`; the
\`BUN_INSTALL\` / \`~/.cherrystudio/install/global/\` path is removed.
- \`FileStorage.getRipgrepBinaryPath()\` now resolves via
\`getBinaryPath('rg')\`; the vendored SDK rg is no longer used.
- \`extractRtkBinaries\` + the \`AgentBootstrapService\` call are
deleted. \`rtkRewrite()\` degrades gracefully when rtk is absent;
\`rtkAvailable\` caches with a 60s TTL so install-via-mise takes effect
without restart.
**Multi-round review**
Two adversarial review rounds against this branch (Bug Hunter / Security
/ Architecture / Correctness) ran during development; both rounds'
High-severity findings are addressed in commits \`70afde6af\` and
\`1d864439d\`. R3 known follow-ups (architecture duplications in
\`CodeCliService\`'s switch statements, etc.) are tracked as Medium and
intentionally deferred.
### Checklist
- [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: Leaves binary acquisition meaningfully cleaner than
before (five mechanisms → one)
- [x] Upgrade: v2 data is throwaway; no migrator needed and the absence
is intentional
- [x] Documentation: \`docs/references/binary-manager/README.md\` +
\`CLAUDE.md\` Architecture section
- [x] Self-review: Two multi-perspective review rounds against the
branch; all Highs addressed
### Release note
\`\`\`release-note
NONE
\`\`\`
---------
Signed-off-by: Vaayne <liu.vaayne@gmail.com>
Signed-off-by: Vaayne Liu <vaayne@macos.shared>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Renderer windows set their logger source inline via initWindowSource() in
entryPoint.tsx, which had to execute before any import-time log or the source
fell back to 'UNKNOWN'. ESM hoists imports above statements, so this ordering
was fragile and applied inconsistently across windows.
LoggerService now derives the source at construction from a
<meta name="logger-window-source"> tag in each window's index.html. The meta
is parsed before any module script runs, so the source is set before any
import-time log -- no ordering rules in entryPoint.tsx. initWindowSource() is
kept as an explicit override for documentless contexts (workers, tests) and
takes precedence over the derived value.
- Add resolveWindowSourceFromMeta() and derivedWindow with
explicit > derived > UNKNOWN precedence; export the LoggerService class
- Declare the meta in all 7 window index.html files (source strings unchanged)
- Drop the 7 inline initWindowSource() calls and the subWindow initLogger.ts
module; keep the worker's explicit initWindowSource('Worker')
- Update windows/README.md and docs/guides/logging.md
- Add LoggerService unit tests
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.