Commit Graph

107 Commits

Author SHA1 Message Date
亢奋猫
366ecf63da refactor(provider): remove tokenflux providers (#16518)
Signed-off-by: kangfenmao <kangfenmao@qq.com>
2026-06-30 19:52:35 +08:00
jd
21e0e04abc feat(resource-dialogs): stepped create wizard for assistants and agents (#16496)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Signed-off-by: jd <59188306+zhangjiadi225@users.noreply.github.com>
2026-06-29 18:25:15 +08:00
fullex
b424475573 feat(cache): add functional updater support to renderer cache hooks
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.
2026-06-29 01:34:05 -07:00
SuYao
611944599f refactor(deps): replace lodash with es-toolkit/compat and drop it (#16528)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
2026-06-29 12:28:57 +08:00
SuYao
5c848feafa fix(custom-fetch): apply provider custom User-Agent request header (#16527)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
2026-06-29 12:13:35 +08:00
fullex
32e8ef273c feat(window-manager): add declarative rememberBounds persistence
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.
2026-06-26 22:39:52 -07:00
亢奋猫
9ed91491c1 refactor(styling): remove styled-components usage (#16370)
Co-authored-by: gujiaming <52187003+AtomsH4@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: kangfenmao <kangfenmao@qq.com>
Signed-off-by: gujiaming <52187003+AtomsH4@users.noreply.github.com>
2026-06-26 18:40:12 +08:00
Phantom
e2f479d912 feat(files): wire file page to real data (#15338)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Signed-off-by: icarus <eurfelux@gmail.com>
2026-06-25 18:09:59 +08:00
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
SuYao
5472d5ecbc refactor(chat): re-add the history records page (inverse of #16281) (#16282)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: jdzhang <625013594@qq.com>
Co-authored-by: gujiaming <52187003+AtomsH4@users.noreply.github.com>
Co-authored-by: kangfenmao <kangfenmao@qq.com>
Co-authored-by: jd <59188306+zhangjiadi225@users.noreply.github.com>
Co-authored-by: fullex <0xfullex@gmail.com>
Signed-off-by: suyao <sy20010504@gmail.com>
Signed-off-by: gujiaming <52187003+AtomsH4@users.noreply.github.com>
Signed-off-by: kangfenmao <kangfenmao@qq.com>
Signed-off-by: jdzhang <625013594@qq.com>
Signed-off-by: jd <59188306+zhangjiadi225@users.noreply.github.com>
2026-06-23 23:30:03 +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
SuYao
32a6666bb3 chore(chat-page): scaffold chat component tree (#14997)
Co-authored-by: jdzhang <625013594@qq.com>
Co-authored-by: gujiaming <52187003+AtomsH4@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: kangfenmao <kangfenmao@qq.com>
Co-authored-by: jd <59188306+zhangjiadi225@users.noreply.github.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Signed-off-by: gujiaming <52187003+AtomsH4@users.noreply.github.com>
Signed-off-by: suyao <sy20010504@gmail.com>
Signed-off-by: kangfenmao <kangfenmao@qq.com>
Signed-off-by: jdzhang <625013594@qq.com>
Signed-off-by: jd <59188306+zhangjiadi225@users.noreply.github.com>
2026-06-23 20:31:49 +08:00
SuYao
e19f7cbc06 feat(chat): carve the v2 chat shell/orchestration layer onto main (#16272)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: kangfenmao <kangfenmao@qq.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Signed-off-by: suyao <sy20010504@gmail.com>
Signed-off-by: kangfenmao <kangfenmao@qq.com>
2026-06-22 23:34:47 +08:00
SuYao
3d0910c673 feat(chat-composer): carve the v2 composer onto main (additive, dormant) (#16260)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Signed-off-by: suyao <sy20010504@gmail.com>
2026-06-22 19:29:08 +08:00
SuYao
1f41a5168a feat(chat): carve the v2 message renderer onto the foundation layer (#16229)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Signed-off-by: suyao <sy20010504@gmail.com>
2026-06-21 20:44:55 +08:00
Gu JiaMing
10a5939e60 fix(emoji-avatar): align sidebar background and support emoji sequences (#16077)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: 顾家铭 <gujiaming@gujiamingdeMacBook-Pro.local>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Signed-off-by: gujiaming <52187003+AtomsH4@users.noreply.github.com>
2026-06-18 23:58:54 +08:00
SuYao
6c61f33c18 feat(data-api): per-topic virtual-root message tree (structural single-root invariant) (#15951)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: jdzhang <625013594@qq.com>
Co-authored-by: jd <59188306+zhangjiadi225@users.noreply.github.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Signed-off-by: suyao <sy20010504@gmail.com>
Signed-off-by: jdzhang <625013594@qq.com>
2026-06-17 18:20:35 +08: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
槑囿脑袋
1382a8dd7c feat(knowledge): route embeddings and reranking through the AI service (#15796)
### 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>
2026-06-09 14:04:29 +08:00
SuYao
5706307451 refactor(ai-service): consolidate AI runtime to main process (#14911)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Signed-off-by: suyao <sy20010504@gmail.com>
2026-06-05 00:06:51 +08:00
Yiran
e8b43bb3a3 refactor(settings): align provider settings UI with design system (#15515)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: 亢奋猫 <kangfenmao@qq.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Signed-off-by: akazaakari950718-dev <akazaakari950718@gmail.com>
2026-06-02 21:38:00 +08:00
Yiran
61c013bd5b feat(knowledge-base): redesign knowledge workspace (#15518)
Co-authored-by: eeee0717 <chentao020717Work@outlook.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Co-authored-by: 槑囿脑袋 <70054568+eeee0717@users.noreply.github.com>
Signed-off-by: akazaakari950718-dev <akazaakari950718@gmail.com>
Signed-off-by: eeee0717 <chentao020717Work@outlook.com>
2026-06-02 16:03:37 +08:00
亢奋猫
26508591f8 refactor(paintings): migrate to v2 data layer and UI (#15154)
Co-authored-by: jidan745le <420511176@qq.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: SuYao <sy20010504@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Signed-off-by: jidan745le <420511176@qq.com>
Signed-off-by: suyao <sy20010504@gmail.com>
2026-06-02 15:18:53 +08:00
fullex
53a3577389 refactor(renderer): flatten src/renderer/src to src/renderer
Move all renderer source from src/renderer/src/* up one level to
src/renderer/*, removing the redundant nested src directory.

- Update path aliases (@renderer, @types, @logger, @data) and TanStack
  Router paths in electron.vite.config.ts; update tsconfig.{json,web,node}
  path mappings and include globs.
- Fix Vite root-relative script paths in the 8 renderer HTML entries.
- Update cross-process relative imports in main/preload (language,
  apiServer models, preload index) to drop the /src segment.
- Switch renderer test imports of the logger mock to the @test-mocks alias.
- Update hardcoded renderer paths in scripts and their fixtures, lint
  configs (eslint/oxlint/biome), CODEOWNERS, docs, and the data-classify tool.
- Convert deep (../../+) relative imports within the renderer to the
  @renderer alias (69 files, 108 imports); keep single-level relatives.
- Fix doc links broken by the move and correct one pre-existing broken
  link in naming-conventions.md.
2026-05-28 21:40:20 -07:00
fullex
fd81de8a32 feat(db-service): add withWriteTx for serialized writes (libsql #288)
libsql client-ts upstream issue #288 makes PRAGMA busy_timeout ineffective
for async transactions, so concurrent db.transaction() calls reliably surface
SQLITE_BUSY. Introduce DbService.withWriteTx as a serialized write helper:

- Process-wide FIFO mutex (async-mutex) serializes write transactions.
- libsql client's default BEGIN IMMEDIATE protects against read-then-write
  tx upgrade failures (no override needed at the drizzle layer).
- Single 50ms BUSY retry guards against transient external locks.

Reads do NOT need this — WAL gives readers snapshot isolation that is never
blocked by writers.

Includes unit tests (FIFO ordering, finally release on throw, single BUSY
retry, persistent BUSY rethrow, non-BUSY passthrough) plus a real-libsql
integration test. Updates the DbService test mock with a passthrough
withWriteTx so dependent services do not throw "is not a function" in
tests. Documents the API in database-patterns.md and points
CLAUDE.md / data-api-overview.md at the new pattern.
2026-05-21 04:54:43 -07:00
Yiran
6a3fa17053 feat(mini-apps): refresh mini app page and icons (#15205)
Co-authored-by: kangfenmao <kangfenmao@qq.com>
Signed-off-by: akazaakari950718-dev <akazaakari950718@gmail.com>
Signed-off-by: kangfenmao <kangfenmao@qq.com>
2026-05-21 11:35:04 +08:00
Phantom
d2c568e349 feat(file): Add schema and foundation for new file module (#13451)
### What this PR does

Adds the **Phase 1a contract surface** for the file module — types, DB
schema, DataApi + File IPC contracts, FileManager skeleton, and
architecture docs.

**Phase 1b.1 (Read Path & Repository), 1b.2 (Write Path & Lifecycle),
1b.3 (Watcher & DanglingCache), and 1b.4 (OrphanSweep &
FileRefCheckerRegistry) are now all landed on top of 1a in this same
PR.** This is the complete Phase 1b runtime — reviewers see the full
read + write + watcher + orphan-sweep picture in one place.

Design, contracts, and decision rationale live in the architecture docs:

-
[`docs/references/file/architecture.md`](docs/references/file/architecture.md)
— module boundaries, type system, IPC/DataApi contracts, layered
architecture, service lifecycle, mutation propagation
-
[`docs/references/file/file-manager-architecture.md`](docs/references/file/file-manager-architecture.md)
— FileManager internals (storage, version detection, atomic writes,
reference cleanup, DirectoryWatcher, orphan sweep, DanglingCache state
machine, key design decisions)

#### Phase 1a deliverables

- Types (`FileEntry` / `FileInfo` / `FileHandle` /
`CanonicalExternalPath` brand)
- DB schema (`file_entry` + `file_ref`) with per-origin CHECK
constraints
- DataApi schemas + stub handlers
- File IPC contract (polymorphic `FileHandle` dispatch;
`batchGetMetadata` included)
- FileManager skeleton + `internal/*` + `ops/*` + `watcher/` +
`DanglingCache` + `versionCache`
- Mutation propagation design (three typed events + prefix-based
queryKey invalidation)

#### Phase 1b.1 deliverables (read-path runtime)

- Shared utilities: `getFileTypeByExt`, `sanitizeFilename`,
`validateFileName` extracted to `@shared/file/types`
- Path utilities: `canonicalizeExternalPath` (NFC + null-byte guard +
trailing-sep strip), `isPathInside`, `isUnderInternalStorage`,
`canWrite`
- FS read primitives: `stat`, `exists`, `read` (text/base64/binary
overloads), `hash` (initial MD5; swapped to xxhash-h64 in 1b.2)
- Metadata utilities: `getFileType(path)`, `isTextFile`, `mimeToExt`
- Repositories: `FileEntryService` + `FileRefService` read methods
(Drizzle-backed, Zod-branded outputs)
- Pure-function modules: `internal/content/{read,hash}`,
`internal/dispatch.ts` (FileHandle dispatcher)
- `toFileInfo(entry)` projection
- `FileManager` class as `BaseService` (`@Injectable('FileManager')`
`@ServicePhase(WhenReady)`); read methods only
- `DanglingCache` + `VersionCache` minimal viable singletons (full impls
in 1b.3 / 1b.2)
- DataApi `/files/*` read handlers fully implemented (entries / single /
ref-counts / refs-by-source)
- 60+ TDD tests (unit + boundary + setupTestDatabase integration)

#### Phase 1b.2 deliverables (write-path runtime)

- **FS atomic primitives** (open to non-file-module consumers per
architecture §5.3): `atomicWriteFile` (tmp + fsync + rename +
fsync(dir)), `atomicWriteIfUnchanged` (re-stat OCC + content-hash
fallback for second-precision mtime), `createAtomicWriteStream`
(Writable wrapper, abort/destroy unlinks tmp)
- **FS general primitives**: `write` (delegates to atomicWriteFile),
`copy` (atomic dest), `move` (rename + EXDEV → copy+unlink fallback),
`remove` (idempotent ENOENT), `mkdir` / `ensureDir` / `removeDir`,
`download` (fetch → atomic stream)
- **Hash swap**: `hash()` migrated MD5 → `xxhash-wasm` h64 streaming;
legacy `md5` dep retained for KnowledgeService loaders
- **VersionCache LRU**: capacity-bounded (default 2000) with
re-insert-on-touch recency
- **Repository mutations**: `FileEntryService.create/update/delete`
(auto UUIDv7 default id, raw DB CHECK errors propagate);
`FileRefService.create/createMany/cleanupBySource/cleanupBySourceBatch`
(`onConflictDoNothing` for batch upsert)
- **internal/entry/**: `create.createInternal` (4 source variants: bytes
/ base64 / path / url) + `create.ensureExternal` (canonicalize + stat +
idempotent upsert + duplicate-suspect peer warn);
`lifecycle.trash/restore/permanentDelete` + batch variants (DB+FS
decoupled, internal best-effort unlink); `rename` (internal DB-only,
external fs.move + canonical externalPath); `copy` (pipes through
createInternal with rollback)
- **internal/content/write.ts**: `write` / `writeIfUnchanged`
(cache-not-trusted re-stat OCC, `StaleVersionError` rewrap from
`PathStaleVersionError`) / `createWriteStream` / `*ByPath` variants
- **internal/system/**: `shell.open` / `shell.showInFolder` (electron
`shell` wrappers); `tempCopy.withTempCopy` (isolated tmp dir; cleanup on
throw)
- **FileManager facade**: every IFileManager mutation method now
delegates to its `internal/*` counterpart (`createInternalEntry` /
`ensureExternalEntry` / `batchCreate*` / `batchEnsure*` / `write` /
`writeIfUnchanged` / `createWriteStream` / `createReadStream` / `trash`
/ `restore` / `permanentDelete` + batch / `rename` / `copy` /
`withTempCopy` / `open` / `showInFolder`); no method throws
notImplemented anymore
- ~60 new TDD tests (each behavior unit = one RED→GREEN→REFACTOR
commit); end-to-end integration scenarios via `setupTestDatabase` cover
atomic-rollback zero-residue, OCC second-precision-mtime
no-false-positive, trash-external CHECK enforcement, full
create→write→read→trash→restore→permanentDelete round-trip, and external
permanentDelete-leaves-user-file-untouched

#### Phase 1b.3 deliverables (watcher + DanglingCache observability)

- **DanglingCache class** (replaces 1a const-literal skeleton):
`byEntryId: Map<entryId, CachedState>` + `pathToEntryIds:
Map<canonicalPath, Set<entryId>>` reverse index, lazy TTL expiration
(default 30 min per architecture §11.2), `forceRecheck` escape hatch,
`Emitter<DanglingStateChangedEvent>` firing only on genuine state
transitions (same-state observations are silent). Injectable `now` /
`statProbe` / `ttlMs` / `fileEntryService` seams for deterministic
tests.
- **createDirectoryWatcher** chokidar v4 wrapper: `add` / `unlink` /
`change` / `ready` / `error` events; built-in OS-junk basename ignores
(`.DS_Store` / `.localized` / `Thumbs.db` / `desktop.ini`); idempotent
`close()`. Factory auto-wires `add` →
`danglingCache.onFsEvent(path,'present')` and `unlink` → `'missing'`.
(Architecture §8.2's richer `onAddDir`/`onUnlinkDir`/`onRename` events
deferred — no consumer needs them in scope.)
- **Reverse-index maintenance from mutation flows**: `ensureExternal`
calls `addEntry` + `onFsEvent('present','ops')` on insert (no-op on
reuse); `permanentDelete(external)` calls `removeEntry`;
`rename(external)` swaps `removeEntry(oldPath) + addEntry(newPath) +
onFsEvent(newPath,'present','ops')`.
- **FileManager surface**: `getDanglingState({id})` (internal →
'present', external → cache check, unknown id → 'unknown');
`batchGetDanglingStates({ids})` (parallel fan-out, unknown ids mapped to
'unknown'); `subscribeDangling({id}, listener)` (in-process per-entry
filter; renderer fan-out via `file-manager-event` IPC channel deferred
to Phase 2).
- **FileManager.onInit**: awaits `danglingCache.initFromDb()` (populates
reverse index from non-trashed external entries; no startup stat probe
per architecture §10.6); registers `File_GetDanglingState` /
`File_BatchGetDanglingStates` IPC handlers via `this.ipcHandle`
(auto-disposed on stop).
- New `IpcChannel` constants: `File_GetDanglingState`,
`File_BatchGetDanglingStates`.
- ~30 new TDD tests across DanglingCache (18 unit) + watcher (6 real-FS)
+ FileManager integration (INT-7..INT-10).

#### Phase 1b.4 deliverables (orphan sweep + FileRefCheckerRegistry)

- **FileRefCheckerRegistry**: `Record<FileRefSourceType,
SourceTypeChecker<...>>` typed registry forces exhaustive coverage at
compile time — adding a new variant to `FileRefSourceType` without a
checker triggers a TS build error. Phase 1 ships `FileRefSourceType =
'temp_session' | 'knowledge_item'`: real DB-backed checker for
`knowledge_item` (Drizzle `inArray` against `knowledge_item`);
`temp_session` checker treats every sourceId as gone (sessions are
in-memory only). `chat_message` / `painting` / `note` are **deliberately
not in the union yet** — each will be added in lockstep (tuple entry in
`allSourceTypes` + `createRefSchema` variant + `SourceTypeChecker`) by
the PR that migrates the owning domain's DB tables to v2. Stray writes
during the migration window fail fast at `FileRefSchema.parse` rather
than being silently persisted under a no-op stub.
- **OrphanRefScanner** (RFC §6.4): `scanOneType(sourceType)` enumerates
distinct `file_ref.sourceId` per type, asks the checker which are alive,
deletes the rest via `cleanupBySourceBatch`. `scanAll()` aggregates
across every registered sourceType. Backed by new
`FileRefService.listDistinctSourceIds` to keep all SQL inside the repo.
- **Report-only orphan-entry pass** (architecture §7.1 default policy is
"preserve"): `scanOrphanEntries` groups active entries with zero
`file_ref` rows by origin. **No deletion** — surfaced via
`getOrphanReport()` for the cleanup-UI consumer. Backed by new
`FileEntryService.findUnreferenced` LEFT JOIN-based query.
- **Startup file sweep** (architecture §10): `runStartupFileSweep`
snapshots `file_entry.id` (active + trashed) into a `Set` via new
`FileEntryService.listAllIds`, walks `{userData}/files/`, plans unlink
for (a) UUID-named files whose id is not in the snapshot and (b)
`*.tmp-<UUID>` atomic-write residue. Applies the `mtime > 5min`
freshness gate (§10.3) — files newer than that are presumed in-flight
and preserved. Plan-then-execute with the `50% / 20-count-floor /
10MB-floor` safety threshold (§10.4); aborts emit
`abortReason='count-fraction'|'byte-fraction'`. Single structured
`orphan-file-sweep` log per run (info / warn / error per outcome,
§10.5).
- **DB-sweep umbrella + observability** (`runDbSweep`): runs scanAll +
scanOrphanEntries, emits one `orphan-sweep` structured record
summarising both passes; failure path returns `outcome='failed'` +
`errorMessage` so callers don't throw on background fire-and-forget.
- **FileManager integration**: `onInit` schedules a fire-and-forget
`runStartupSweeps` that runs the FS-level + DB-level sweeps in parallel;
failures of either are logged but never block ready. `getOrphanReport()`
exposes the most recent `DbSweepReport` (orphan-ref counts already
cleaned + orphan-entry counts preserved) + `lastRunAt` for the cleanup
UI surface.
- ~30 new TDD tests across registry (14 unit) + orphan sweep (16 unit +
integration) + FileManager integration (INT-11/INT-12) + repo
(`findUnreferenced`, `listAllIds`, `listDistinctSourceIds`).

**Out of scope (deferred to Phase 2)**:
- Architecture §7.2 dangling-external auto-cleanup (external + missing +
0-ref + >30d retention) — narrow extension shipping with the cleanup UI.
- Adding `chat_message` / `painting` / `note` as `FileRefSourceType`
variants (tuple entry + schema + checker added together) — gated on each
domain's v2 batch migration.
- Cleanup-UI surface that consumes `getOrphanReport()` — Phase 2
renderer work.

Renderer-side File IPC bridge for write/dangling methods stays deferred
to Phase 2 alongside the consumer-batch migrations. The Phase 1b runtime
is consumable from main-side business services through
`application.get('FileManager')`.

### Why we need it and why it was done in this way

Contract-first concentrates design review in one place; Phase 1b.x then
becomes pure "honor the contracts". Each 1b.x phase keeps strict TDD
(RED → GREEN → REFACTOR per behavior, ~one commit per cycle); each phase
ends with a verification gate (push → CI green) before the next phase
begins.

Core decisions (origin two-state, `FileEntry`/`FileInfo` split, DataApi
SQL-only, external `permanentDelete` DB-only, TTL `DanglingCache`, OCC
trust boundary, atomic write fsync default, etc.) and their rationale
are recorded in `file-manager-architecture.md §12 Key Design Decisions`
— not duplicated here.

### Breaking changes

None — purely additive (read+write paths are new, no existing callers
replaced yet).

### Special notes for your reviewer

- Review focus: contracts (1a) + read-path runtime (1b.1) + write-path
runtime (1b.2) + watcher/DanglingCache (1b.3) + orphan sweep / registry
(1b.4). Phase 1b is now complete on this branch.
- **Phase 1a contract stability policy** (architecture.md top) is
binding — any 1b.x PR that finds a contract mismatch PRs the doc
revision first.
- Deferred (Phase 2): renderer-side File IPC bridge for
write/dangling/orphan methods (alongside consumer migration); cleanup-UI
surface consuming `getOrphanReport()`; architecture §7.2
dangling-external auto-cleanup (>30d retention); adding `chat_message` /
`painting` / `note` as `FileRefSourceType` variants (each adds tuple
entry + schema + checker in lockstep, gated on the owning domain's v2
batch migration); DanglingCache periodic snapshot logger (architecture
§11.8); `listDirectory` ripgrep wrapper; `compressImage`
(KnowledgeService consumer); FileUploadService + `file_upload` table
(Vercel AI SDK Files API).

### 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: You have left the code cleaner than you found it (Boy
Scout Rule)
- [ ] Upgrade: N/A — purely additive
- [ ] Documentation: Internal architecture docs included
(`docs/references/file/`); no user-facing docs change
- [x] Self-review: I have reviewed my own code before requesting review
from others

### Release note

```release-note
NONE
```

---------

Signed-off-by: icarus <eurfelux@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
2026-05-13 16:06:10 +08:00
槑囿脑袋
8b02272bcd refactor(web-search-settings): migrate settings to v2 data path (#14993)
### What this PR does

Before this PR:

Web Search settings still depended on legacy renderer-side settings
wiring and provider-specific UI structure. Provider selection, API key
editing, blacklist handling, and compression settings were split across
legacy hooks/routes and were not aligned with the v2 preference/provider
configuration model.

After this PR:

Web Search settings are migrated to the v2 data and architecture path:

- Adds `useWebSearch` as the renderer settings hook backed by v2
preference APIs.
- Rebuilds the Web Search settings page around smaller components,
hooks, and utilities for provider metadata, API keys, defaults,
blacklist, and provider checks.
- Uses v2 provider presets and resolved provider capability data for
keyword search and URL fetch defaults.
- Updates preference mappings/classification for removed or normalized
Web Search settings.
- Adds focused tests for the new settings hooks, provider metadata, API
key handling, blacklist parsing, and provider check behavior.
- Documents user-visible v2 Web Search setting changes in the breaking
changes log.

<!-- (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 v2 refactor is moving settings and provider configuration away from
legacy renderer-local state. Web Search settings need to read and write
through the same v2 preference source of truth as the main-side Web
Search service so the UI, migration layer, and runtime configuration use
one provider model.

The following tradeoffs were made:

- The PR keeps the work scoped to Web Search settings and the
preference/provider metadata needed by that settings surface.
- Deprecated settings are removed instead of preserved through
compatibility UI, because v1 Web Search settings are throwaway during
the v2 refactor.
- The settings page is split into local hooks/components instead of
introducing a broader settings framework abstraction.
- Runtime service adjustments are limited to what is required by the
settings schema and preference changes.

The following alternatives were considered:

- Keeping the old `useWebSearchProviders` path as a compatibility layer,
but that would continue dual settings ownership during the v2 migration.
- Preserving the removed subscription-source and cutoff-unit UI, but
those settings do not map cleanly to the v2 Web Search configuration
model.
- Combining this with runtime Web Search tool behavior changes, but that
would make the PR too broad and harder to review.

Links to places where the discussion took place: N/A

### Breaking changes

This PR removes deprecated Web Search settings from the v2 settings
surface:

- Web Search subscription-source management is removed.
- Compression cutoff is normalized to a token-based cutoff limit; the
cutoff unit selector is removed.

Impact: users will configure Web Search through v2 provider defaults,
API keys, capability hosts, blacklist, and compression settings. No
manual migration action is expected.

### Special notes for your reviewer

Scope of this PR:

- Web Search settings UI migration to v2 data/preferences.
- Renderer settings hooks/components/utilities for Web Search provider
configuration.
- Preference classification/mapping changes required by the settings
migration.
- Tests for the settings-side behavior introduced here.

Explicitly out of scope:

- Web Search runtime orchestration in chat responses.
- Built-in/external Web Search tool execution behavior.
- Message rendering for Web Search tool results or citations.
- Model switching behavior and assistant-level `enableWebSearch`
semantics outside the settings migration.
- Large-scale UI design-system replacement beyond the files touched for
this settings migration.
- New provider integrations or provider search algorithm changes.

Review scope note: this PR is based on `v2`, but the intended review
scope is limited to the Web Search settings migration described above.
Web Search runtime/service/tool changes that are already part of the
prerequisite v2 Web Search work are not part of this PR's review scope.

Validation:

- Not run in this PR creation pass.

### 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] 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)
- [x] 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
- [x] 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.
Present in `v2-refactor-temp/docs/breaking-changes/`.
- [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
Web Search settings now use the v2 preference/provider configuration path. Deprecated subscription-source settings were removed, and compression cutoff is now token-based.
```

---------

Signed-off-by: eeee0717 <chentao020717Work@outlook.com>
2026-05-12 14:16:18 +08:00
jd
64d6c84535 refactor(renderer-components): replace legacy antd and styled UI usage (#14940)
Co-authored-by: SuYao <sy20010504@gmail.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
2026-05-09 23:10:23 +08:00
槑囿脑袋
01e7e31a8e feat(v2): add main-side file processing backend (#13968)
### What this PR does

Before this PR:

File processing on the `v2` branch was still described and wired around
split OCR / markdown APIs, legacy feature names, and feature-first
provider structure. OCR-like image text extraction and
document-to-markdown conversion did not share one task contract, and
provider task ids / polling details were harder to keep behind the
Main-process boundary.

After this PR:

`v2` file processing follows
`v2-refactor-temp/docs/fileProcessing/file-processing-service.md` as the
design baseline:

- exposes one Main-side task API: `startTask`, `getTask`, and
`cancelTask`
- replaces split file-processing IPC with `file-processing:start-task`,
`file-processing:get-task`, and `file-processing:cancel-task`
- renames features and preference keys to `image_to_text` and
`document_to_markdown`
- adds `FileProcessingTaskService` as the in-memory source of truth for
task ids, task state, progress, cancellation, TTL pruning, remote-poll
dedupe, and task change events
- keeps provider task ids, remote context, query context, abort
controllers, and in-flight polling inside Main-process task records
- maps completed results to artifacts: inline `text/plain` for
`image_to_text`, and persisted markdown file artifacts for
`document_to_markdown`
- reorganizes providers into processor-first handlers under
`src/main/services/fileProcessing/processors`
- moves Tesseract worker ownership under `processors/tesseract/runtime`
- removes the new file-processing module's old `ocr/` and `markdown/`
split directories after migrating their logic
- updates shared schemas, presets, preference generation, migration
mappings, and tests for the renamed feature model

The public file-processing contract is now:

```ts
await window.api.fileProcessing.startTask({
  feature: 'image_to_text',
  file,
  processorId: 'tesseract'
})

await window.api.fileProcessing.getTask({ taskId })
await window.api.fileProcessing.cancelTask({ taskId })
```

Architecture overview:

```text
Renderer / upper-layer caller
        |
        | startTask / getTask / cancelTask
        v
FileProcessingOrchestrationService
        |
        | Zod validation + delegation
        v
FileProcessingTaskService
        |
        | taskId, task store, TTL, cancellation,
        | background execution, remote polling, artifacts
        v
processorRegistry[processorId].capabilities[feature]
        |
        +--> image-to-text handlers
        |       -> text/plain artifact
        |
        +--> document-to-markdown handlers
                -> feature.files.data/fileId/file-processing/taskId/output.md
```

Notes:

- The new file-processing API does not keep facades for
`file-processing:extract-text`,
`file-processing:start-markdown-conversion-task`, or
`file-processing:get-markdown-conversion-task-result`.
- `FileProcessingOrchestrationService` is intentionally only the IPC
validation and delegation layer.
- Task state is Main-process runtime coordination state, not DataApi or
Cache state.
- Renderer task subscriptions, a global UI task center, and full
renderer business-flow migration are intentionally out of scope for this
PR.
- The legacy standalone OCR path outside the new file-processing module
can coexist during the v2 transition, but the new file-processing
interface is not polluted by those split-API types.

Fixes #N/A

### Why we need it and why it was done in this way

This PR makes OCR-style image text extraction and document-to-markdown
conversion use the same Main-process task model before renderer-side
adoption. The unified contract gives upper layers one way to start work,
query progress, handle failure, cancel work, and consume completed
artifacts without learning provider-specific polling details.

The following tradeoffs were made:

- Fast OCR now also goes through a task API, so callers need start/query
behavior instead of a direct `extractText -> text` call.
- Task state remains session-scoped in memory; completed artifacts are
persisted, but task snapshots are not restored after app restart.
- Remote-provider cancellation is best-effort: local polling and state
transition stop immediately, but third-party provider-side cancellation
is not guaranteed.
- Renderer integration is intentionally compile-safe and minimal in this
PR; full UX migration should happen in follow-up changes.
- Tesseract keeps a processor-owned runtime service, while other
processors stay as handlers/utilities until they need lifecycle-managed
resources.

The following alternatives were considered:

- Keeping separate OCR and markdown conversion APIs, which would
preserve the current split but continue duplicating task, progress,
cancellation, and result semantics.
- Adding a DataApi task table or Cache mirror for file-processing task
state, which would create a second source of truth for runtime
coordination state.
- Adding renderer push subscriptions in this PR, which would expand the
scope beyond the Main-side task contract.
- Introducing a generic process manager for all processors, which is
premature while only Tesseract currently owns reusable lifecycle
resources.

Links to places where the discussion took place:
`v2-refactor-temp/docs/fileProcessing/file-processing-service.md`

### Breaking changes

None for released user-facing behavior.

If this PR introduces breaking changes, please describe the changes and
the impact on users.

For the in-progress `v2` file-processing integration, this replaces the
split file-processing IPC/preload shape with the unified
`startTask/getTask/cancelTask` contract. It also renames file-processing
feature and preference keys from the old `text_extraction` /
`markdown_conversion` model to `image_to_text` / `document_to_markdown`.

### Special notes for your reviewer

- This PR targets `v2`, not `main`.
- Review this against
`v2-refactor-temp/docs/fileProcessing/file-processing-service.md`; that
document is the source of truth for the module boundary.
- Main review points: unified task API, artifact model, cancellation
semantics, processor-first registry/handlers, hidden provider runtime
state, and no DataApi/Cache task storage.
- `document_to_markdown` artifacts are persisted under
`application.getPath('feature.files.data')/fileId/file-processing/taskId/output.md`.
- `image_to_text` artifacts are returned inline as plain text artifacts
and are not persisted.
- Current local verification status:
  - `pnpm format`: passed
  - `pnpm build:check`: passed
- Vitest inside `build:check`: `432` test files passed, `7171` tests
passed, `72` skipped

### 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] 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)
- [x] 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

```release-note
NONE
```

---------

Signed-off-by: eeee0717 <chentao020717Work@outlook.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
2026-05-08 21:25:03 +08:00
亢奋猫
dee3bb0928 feat(settings): refactor settings UI and add settings window (#14567)
Signed-off-by: kangfenmao <kangfenmao@qq.com>
Signed-off-by: jdzhang <625013594@qq.com>
Co-authored-by: jdzhang <625013594@qq.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
2026-05-08 18:09:26 +08:00
槑囿脑袋
63bcabf3da refactor(web-search): split provider capabilities and add Fetch/Jina (#14856)
Co-authored-by: SuYao <sy20010504@gmail.com>
2026-05-08 17:08:20 +08:00
亢奋猫
60efa15e98 refactor(websearch-settings): remove local providers and use v2 preferences (#14443)
Co-authored-by: jdzhang <625013594@qq.com>
2026-05-06 13:53:40 +08:00
LiuVaayne
8d3ce3bfb1 refactor(agents): migrate renderer agent hooks from HTTP to DataApi (#14431)
Co-authored-by: SuYao <sy20010504@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 19:13:36 +08:00
SuYao
1ccb306b30 feat(topics): migrate ordering + pin to canonical fractional-indexing pattern (#14627)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
2026-04-30 21:18:25 +08:00
fullex
5677ab62bd refactor(data-api): tighten R1/R3 violations in agent/topic/message data
Apply NOT NULL constraints where NULL has no domain meaning:
- agent / agentSession: description, instructions, mcps, allowedTools,
  configuration, accessiblePaths, slashCommands (session only)
- agentGlobalSkill.tags
- message.searchableText, message.siblingsGroupId
- topic.name, topic.{isNameManuallyEdited, sortOrder, isPinned, pinnedOrder}
- miniapp.sortOrder

Drop rowMapper "?? fallback" patterns; preserve genuine T|null contracts
(agentSessionMessage.agentSessionId now passes NULL through, with the Zod
entity tightened to .nullable() to match).

Migrate product-chosen DB DEFAULTs to the service layer:
- agentTask.status DB DEFAULT removed; service was already supplying 'active'
- agentGlobalSkill.isEnabled DB DEFAULT flipped from true to false to match
  SkillService.install behavior

Drop Zod .default([]) from CreateAgentSchema.accessiblePaths so the
service-layer computeWorkspacePaths() is the single runtime default source.

Update FTS5 triggers to COALESCE the group_concat result to '' so
messages with no main_text blocks don't violate the new NOT NULL on
searchable_text.

Refs: docs/references/data/best-practice-default-values-and-nullability.md
2026-04-29 08:35:46 -07:00
fullex
ad2b402c04 feat(cache-service): add main-process subscribe API with template key support
Enables main-process services to react to cache changes without writer-side
wiring — unblocks the web-search/OCR provider rotation use case where new
providers would otherwise require manual hook-ups at every write site.

Also unifies equality across main and renderer on lodash.isEqual (fixing
redundant cross-window broadcasts when Record/Array values are rebuilt on
every write) and extracts template utilities to the shared package so both
processes use one implementation.
2026-04-22 22:04:06 -07:00
fullex
3fbc52e056 refactor(cache-shared): support template keys and drop sharedCasual API
Align SharedCache type system with Memory (UseCache) template support and
remove the sharedCasual escape hatch that existed only because SharedCache
could not type-check dynamic keys:

- Introduce InferSharedCacheValue and expand SharedCacheKey through
  ProcessKey so template schema entries like
  'web_search.provider.last_used_key.${providerId}' match concrete keys
  with precise value types on both Main and Renderer.
- Extend useSharedCache hook with findMatchingSharedCacheSchemaKey /
  getSharedCacheDefaultValue, mirroring useCache's template-aware default
  resolution.
- Remove getSharedCasual / setSharedCasual / hasSharedCasual /
  deleteSharedCasual / hasSharedTTLCasual from the Renderer CacheService
  and all test mocks; Main CacheService never had them.
- Migrate BaseWebSearchProvider and OcrBaseApiClient rotation from
  sharedCasual to type-safe getShared/setShared. Rename keys to conform
  to schema naming rules (ESLint data-schema-key/valid-key):
    web-search-provider:${id}:last_used_key
      -> web_search.provider.last_used_key.${providerId}
    ocr_provider:${id}:last_used_key
      -> ocr.provider.last_used_key.${providerId}
  Old values under legacy key names become orphans after rollout; this
  is acceptable because rotation state is transient and consumers
  reinitialize from keys[0] on a miss.
- Add type-level assertions for SharedCacheKey and InferSharedCacheValue
  in useCache.types.test.ts to lock the contract in CI.
- Update cache-overview.md, cache-usage.md, and tests/__mocks__/README.md
  to describe SharedCache's template support and reflect that casual
  methods now exist only on the Memory tier.
2026-04-22 19:25:54 -07:00
fullex
9d1e65c32f test(data-api): add useReadCache/useWriteCache coverage and shared mocks
The hooks shipped in 87d1a947a without test coverage or team-facing
mocks. Consumer tests would otherwise need to hand-roll an SWRConfig
wrapper or skip asserting cache-control semantics entirely. This adds:

- Direct unit tests that exercise the real hooks inside a self-provided
  SWRConfig (covering key shape, empty-query folding, no-revalidate
  writes, non-reactive reads, and read/write round-trip).
- Mock factories in tests/__mocks__/renderer/useDataApi.ts backed by a
  shared in-memory Map, plus seedCache/getCachedValue/clearCache
  helpers on MockUseDataApiUtils. Registered in MockUseDataApi so the
  global renderer setup picks them up without further wiring.
- Docs update in tests/__mocks__/README.md with signatures and usage.
2026-04-21 04:21:23 -07:00
fullex
686bd15290 feat(data-api): support template paths, function refresh, and /* prefix matching
Extend the renderer data hooks to cover three mutation shapes that
previously had to drop to imperative dataApiService calls:

- Template paths (e.g. `/providers/:providerId`) with a runtime `params`
  option on useQuery / useMutation / useInfiniteQuery / usePaginatedQuery,
  so a single hook instance can operate on ids chosen at call time.
  ParamsForPath derives types directly from the schema's existing
  `params: {...}` declarations (no template-string parsing).
- Function-form `refresh: ({ args, result }) => ConcreteApiPaths[]` for
  invalidation keys that depend on trigger input or server response. Args
  are closure-captured at trigger entry to avoid races between concurrent
  calls.
- Explicit `/*` suffix for path-segment prefix matching on refresh and
  invalidate patterns, preserving the trailing slash so `/providers/*`
  doesn't match siblings like `/providers-archived`.

A single `resolveTemplate` function is the canonical path-replacement
point, so `useQuery('/providers/:id', { params: { id: 'abc' } })` and
`useQuery('/providers/abc')` produce byte-for-byte identical cache keys.

Dev-mode assertions flag invalid `/*` patterns (bare wildcard or missing
trailing slash) and warn on concurrent triggers against the same template
hook instance, which would share SWR mutation state.

Fully backward compatible: existing `refresh: ['/topics']` and
concrete-path hook calls compile and behave identically.
2026-04-20 09:02:57 -07:00
fullex
826a453579 docs(test-mocks): clarify scope boundary and document lifecycle service testing
The mocks README described what was mocked but never stated that
tests/__mocks__/main/ is intentionally limited to cross-cutting
infrastructure. Without that boundary in writing, feature-specific
lifecycle services were getting registered into defaultServiceInstances,
bloating the global default surface without serving any other test.

Adds an explicit Scope section with a pattern-selection table, documents
the canonical local-stub pattern for feature-specific lifecycle services
(vi.mock on @application + MockBaseService stand-in), and a Common
Assertions table covering phase, dependencies, IPC handlers, and
disposables. Adds the missing Main PreferenceService section to match the
other three infrastructure service docs. Links the lifecycle README to
the new testing guidance.
2026-04-20 06:31:55 -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
aff9b934b4 refactor(main): migrate quick window into WindowManager
- Register QuickAssistant in windowRegistry (singleton, show:false,
  showInDock:false, macReapplyAlwaysOnTop quirk). QuickAssistantService
  now creates the BrowserWindow via wm.create() and sets up listeners
  directly after creation; no global onWindowCreated subscription needed
  since we own the window.
- Instantiate windowStateKeeper before wm.create() and inject persisted
  x/y/w/h via OpenWindowArgs.options. manage() only attaches outbound
  listeners and does not retroactively apply persisted bounds.
- Set the initial alwaysOnTop level once after creation; the
  macReapplyAlwaysOnTop quirk handles re-application across hide/show.
- closeQuickWindow now hides instead of destroying. Quick window is a
  high-frequency toggle; destroy+recreate would blank-flash on next show
  and waste preload work. Final teardown stays in WindowManager.onDestroy.
- Rename WindowType.Mini -> QuickAssistant, IpcChannel.MiniWindow_* ->
  QuickAssistant_*, IpcChannel.ShowMiniWindow -> QuickAssistant_Shown,
  preload window.api.miniWindow -> quickAssistant, log window source
  'MiniWindow' -> 'QuickAssistant'. Drop dead QuickAssistant_Hidden IPC
  (no listener exists anywhere in the tree).
- Cross-process contracts retained: miniWindow.html entry and
  miniWindow-state.json bounds file are kept verbatim to avoid a build
  layout change and to preserve every user's saved window bounds.
- Update renderer call sites (HomeWindow, QuickAssistantSettings,
  entryPoint) and an e2e fixture comment to the new namespace.
2026-04-18 05:50:12 -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
8c83072887 feat(test-infra): introduce setupTestDatabase harness
Adds a unified, file-backed SQLite test harness under tests/helpers/db/
that provisions a real database per test file using the production
migrations + CUSTOM_SQL_STATEMENTS, wires it through
MockMainDbServiceUtils.setDb() so production code reaches it via
application.get('DbService').getDb() transparently, truncates user
tables on beforeEach, and cleans up on afterAll. Replaces the need for
hand-written CREATE TABLE SQL and inline vi.mock('@application')
overrides in consumer tests.

Companion changes:

- Register @test-helpers/* path alias (electron.vite.config.ts,
  tsconfig.node.json paths + include, vitest.config.ts main project
  include).
- Relax the global vi.mock stubs for node:fs, node:os, node:path in
  tests/main.setup.ts so third-party libraries (drizzle-orm migrator
  etc.) can read real files. The six existing tests that depended on
  the previous fully-stubbed modules now declare a local
  vi.mock('node:fs', ...) via the new createNodeFsMock helper at
  tests/helpers/mocks/nodeFsMock.ts.
- Self-tests under tests/helpers/db/__tests__/testDatabase.test.ts
  cover PRAGMA state, truncate semantics, FTS5 trigger cascades,
  NULL-searchableText handling, application-mock routing, and
  replay-array accumulation guards.
2026-04-15 09:33:37 -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