mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-09 01:04:44 +08:00
6ec914cf0ffdf0dbbcd7bfdfd2c3ce33320d3866
10 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3a508c987b |
refactor(miniapp): migrate to v2 data layer (#14049)
### What this PR does
Migrates the MiniApp feature from v1 (Redux + sidecar
`custom-minapps.json`) to the v2 data architecture (DataApi + Preference
+ Cache), and integrates it into the v2 AppShell tab system.
**Before this PR**
- App lists lived in three Redux arrays (`enabled` / `disabled` /
`pinned`); custom-app logos were stripped before persistence and
recovered at runtime from `{userData}/Data/Files/custom-minapps.json`.
- Settings (`region`, `max_keep_alive`, `open_link_external`,
`show_opened_in_sidebar`) lived in legacy redux/electron-store.
- Runtime keep-alive used a module-level `lru-cache` singleton, mirrored
into v2 cache via `onInsert` / `disposeAfter` (two sources of truth —
already a known race).
- Routes were `/app/minapp/*`; sidebar icon literal was `'minapp'`.
- Sidebar mode used the legacy popup container; top-navbar mode was
non-functional.
**After this PR**
- A single `mini_app` SQLite table owns every row (preset + custom).
Preset rows are seeded by `MiniAppSeeder` from `PRESETS_MINI_APPS` on
every boot; custom rows come in via `POST /mini-apps`. The seeder uses
`setWhere isNotNull(presetMiniappId)` so refreshing preset display
fields can never overwrite a custom row whose `appId` happens to collide
with a preset.
- `MiniAppMigrator` imports v1 Redux state and reads
`custom-minapps.json` (path resolved through
`MigrationPaths.customMiniAppsFile`) to recover stripped logos.
- Settings live under typed Preference keys
(`feature.mini_app.{region,max_keep_alive,open_link_external}`); sidebar
icon literal renamed `'minapp'` → `'mini_app'` with a complex preference
transform that rewrites existing user arrays in-place.
- API: `GET/POST/PATCH/DELETE /mini-apps` + `POST
/mini-apps/order:batch`, Zod-validated, fractional-indexing ordering
scoped by `status` (cross-status batches are rejected with
`VALIDATION_ERROR` per the data-ordering-guide contract). Status
transitions reassign `orderKey` to the tail of the target partition
inside a transaction.
- Renderer hook `useMiniApps` exposes **command-style** writes only:
`updateAppStatus(id, status)` and `setAppStatusBulk([{id, status}])`.
The legacy declarative `updateMiniApps(list)` /
`updateDisabledMiniApps(list)` / `updatePinnedMiniApps(list)` are gone —
they took region-filtered subsets and silently disabled rows the caller
never saw.
- Keep-alive list is stored solely in
`useCache('mini_app.opened_keep_alive')`. Cap eviction respects AppShell
pin status: `useMiniAppPopup` reads pinned mini-app routes from
`useTabs` and skips them in eviction. `MiniAppTabsPool` renders webviews
in a stable `appId`-sorted order so LRU reorders never move `<webview>`
DOM nodes (Electron `<webview>` loses its guest WebContents on
detach/reattach).
- **Unified launch path**: clicking any miniapp (from the launcher grid
or a top tab bar entry) calls `openTab('/app/mini-app/<id>', { title,
icon: app.logo })`. A globally-mounted `<MiniAppTabsPool>` in `AppShell`
keeps a `<webview>` alive per opened app, regardless of sidebar vs
top-navbar layout.
- Settings UI rewritten as a `PageSidePanel` drawer composed of
`MiniAppListPair` (visible / hidden columns with drag-drop) and
`MiniAppDisplaySettings` (region / cache slider). New custom-app form is
a separate `NewMiniAppPanel` drawer.
- Sidebar's running-mini-apps strip removed — opened apps live
exclusively in the top tab bar (per #3198804265). Companion preference
`feature.mini_app.show_opened_in_sidebar` deleted from the schema.
### Why we need it and why it was done in this way
Part of the broader v2 data-layer migration (Redux/Dexie/ElectronStore →
DataApi + Preference + Cache).
**Architecture**
- DataApi for entity rows (preserves user content); Preference for
atomic settings; Cache (Memory tier) for runtime ephemera.
- Layered preset pattern (`best-practice-layered-preset-pattern.md`):
preset and custom rows share the same table, discriminated by
`presetMiniappId`. Seeder refreshes preset display fields on re-run;
custom rows are immutable to the seeder.
- Region filtering is a **view-only** concern (read path); the write
path is command-style and never references region. This eliminated a
class of bugs where editing the visible (filtered) list caused
region-hidden rows to drift.
- AppShell tab pinning is the canonical "keep this loaded" signal. The
keep-alive cap respects it; pinned mini-app tabs never get evicted
regardless of cap. Render-order independence in `MiniAppTabsPool`
ensures LRU touches don't move `<webview>` nodes around.
- Per-app icon resolution: `app.logo` is a `CompoundIcon` id (e.g.
`"Moonshot"`) for presets and a URL for custom apps. UI consumers (tab
bar, sidebar entry, settings list) call `getMiniAppsLogo` to resolve the
id to a `CompoundIcon` before rendering, with `<img>` fallback for URL
strings.
- Per-entity tab icons are cleared on internal navigation, sidebar
reuse, and the top-bar settings button — three call sites that all flip
the active tab's URL now consistently reset `icon: undefined` so a
mini-app logo never sticks onto an unrelated route.
**Tradeoffs**
- `useMiniApps` still exposes `miniapps` (region-filtered
enabled+pinned) and `disabled` (region-filtered). These are display-only
views. Renamed/typed wrappers were considered but deferred — the
refactor to command-style writes already eliminated the bug class that
motivated the rename.
- The `applyReorderedList` integration test for
`reorderMiniAppsByStatus` was dropped — `MockUseDataApiUtils` doesn't
fill the SWR cache that `useReorder.readCurrent` reads. Splice logic is
straightforward and the server-side `applyScopedMoves` test covers the
contract.
- Sidebar primitives in `@cherrystudio/ui`-adjacent layout still accept
`miniAppTabs` / `onMiniAppTabClick` props (defensive defaults — render
nothing without a producer). Removing these from the primitive's API is
a separate refactor not in scope.
### Breaking changes
User-visible changes are auto-migrated by the v2 migration framework —
no manual user action required:
- Sidebar icon literal `'minapp'` → `'mini_app'` (rewritten by the
`sidebar_icons_rename` complex preference transform)
- Preference key rename `feature.minapp.*` → `feature.mini_app.*`
(auto-migrated via `classification.json`)
- Custom-app logos stripped from v1 Redux are recovered from
`custom-minapps.json` during migration
One product-shape change is documented under
`v2-refactor-temp/docs/breaking-changes/`:
- `2026-05-07-miniapp-sidebar-running-list-removed.md` — the sidebar no
longer surfaces opened mini-apps under the mini-app entry. Open apps are
accessed exclusively via the top tab bar; pin a tab to keep its state
across switches.
The legacy v1 preference `showOpenedMinappsInSidebar` is reclassified as
`status: deleted` in the migration pipeline; v1 values are dropped
during v1→v2 migration with no v2 destination.
### Special notes for your reviewer
**Verified end-to-end on a real dev profile**: v1 Redux state +
`custom-minapps.json` → v2 SQLite, including pinned-app cross-group
dedup (a v1 pinned app appears in both `pinned` and `enabled` Redux
arrays; the migrator counts duplicates as skipped so the engine's
`targetCount >= sourceCount - skippedCount` invariant holds — without
this, any user with pinned miniapps was blocked from migrating).
**Drizzle migrations** are throwaway in dev per `CLAUDE.md`.
`migrations/sqlite-drizzle/0020_even_hulk.sql` is the single regenerated
migration; it will be wiped to a clean initial migration before release.
**Review history**: 28 line-comments across multiple formal review
rounds. All resolved. The most consequential fixes:
- `applyScopedMoves` in `MiniAppService.reorder` — rejects cross-status
batches with `VALIDATION_ERROR` instead of silently splitting them.
- `update()` reassigns `orderKey` to a fresh tail in the target
partition on status change.
- Empty-string substitution in migrator mappings is now caught by the
post-transform validity check; bad rows are skipped + warned, never
inserted.
- Migrator validation switched from `limit(5)` sample to full `count(*)
WHERE empty-fields` — bad rows can no longer pass validation by virtue
of being beyond the sample window.
- Keep-alive cap exempts pinned tabs (#3198809321 + the kangfenmao
keepalive review); render order in `MiniAppTabsPool` is `appId`-stable
so LRU touches don't move `<webview>` nodes (this was the root cause of
"switching tabs reloads the webview").
**Out of scope**:
- The remaining `@renderer/store/tabs` import in
`PaintingsRoutePage.tsx` is pre-existing v1 residual (not introduced or
touched by this PR).
### Checklist
- [x] PR: description rewritten to reflect the final architecture +
integration with the AppShell tab system
- [x] Code: command-style writes (`updateAppStatus` /
`setAppStatusBulk`); see `useMiniApps`, `MiniAppService`,
`MiniAppMigrator`, `MiniAppTabsPool`, `useMiniAppPopup` for the main
entry points
- [x] Refactor: ~1500 lines of dead/legacy code removed
(`Tab/TabContainer`, `TabsService`, `MiniAppPopupContainer`,
`TopViewMiniAppContainer`, legacy LRU singleton, `PinnedMiniApps`, dead
`userOverrides` / `MiniAppRegistryService`, unused `Signal.ts`)
- [x] Upgrade: v1 → v2 migration verified end-to-end on a real dev
instance
- [x] Documentation: architecture covered by `docs/references/data/`;
one user-visible behavior change documented in
`v2-refactor-temp/docs/breaking-changes/`
- [x] Self-review: multi-agent review via `/gh-pr-review` (twice); all
28 review comments resolved
### Release note
```release-note
NONE - Internal v2 data refactor. User-facing renames (route, sidebar icon, preference keys) are auto-migrated. The sidebar no longer shows a running-mini-apps strip; opened apps live in the top tab bar.
```
---------
Signed-off-by: suyao <sy20010504@gmail.com>
Signed-off-by: chengcheng84 <hello_world0000@outlook.com>
Co-authored-by: suyao <sy20010504@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Co-authored-by: Copilot <copilot@github.com>
|
||
|
|
60efa15e98 |
refactor(websearch-settings): remove local providers and use v2 preferences (#14443)
Co-authored-by: jdzhang <625013594@qq.com> |
||
|
|
2456dbdf96 |
feat(model-selector-v2): build v2 model selector on new data/UI stack (#14490)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: SuYao <sy20010504@gmail.com> Co-authored-by: 亢奋猫 <kangfenmao@qq.com> Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com> Fixes #14483 |
||
|
|
a4309aad61 |
feat(migration): migrate llm model preferences to v2 with UniqueModelId format (#14229)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com> |
||
|
|
ea99c82115 |
refactor: remove memory module from v2 (#14251)
Co-authored-by: SuYao <sy20010504@gmail.com> Fixes #14250 |
||
|
|
4197117b51 |
refactor: migrate shortcut system to Preference architecture (#14086)
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com> |
||
|
|
13b333e9b3 |
feat(v2): decouple assistant from topic with dedicated table and migration (#13851)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com> |
||
|
|
44b5edce54 |
feat(preboot): add core/preboot subsystem for userData location
Introduce a new `src/main/core/preboot/` subsystem that owns the synchronous setup happening before `application.bootstrap()` runs. This is the follow-up promised by |
||
|
|
c17dd033fc |
feat(migration): migrate v1 ~/.cherrystudio/config/config.json into BootConfig
Add a 'configfile' source kind to BootConfigMigrator so the v1 home config
file's `appDataPath` is migrated into a new BootConfig key
`app.user_data_path: Record<string, string>`, keyed by executable path.
v1's `~/.cherrystudio/config/config.json` is conceptually "early-boot
configuration that must be readable before userData is decided" — the same
category as BootConfig. v1 `config/config.json` will eventually be replaced
entirely by BootConfig; this PR establishes the data migration path, and a
follow-up will rewire `initAppDataDir()` to read from BootConfig instead.
Key pieces:
- New `LegacyHomeConfigReader` — sync fs read, normalizes both the legacy
string and the array-of-{executablePath,dataPath} v1 formats into a
`Record<string, string>`. Returns `null` (not `{}`) for "no data" so the
migrator's null-skip guard distinguishes "missing" from "empty record"
and doesn't write a spurious empty entry.
- BootConfigMigrator: new 'configfile' branch in `prepare()`; inline
`configFileMappings` local const with `targetKey: BootConfigKey` type
annotation (regen safety net — schema loses `app.user_data_path` →
compile error at the declaration site). `defaultValue: null` on
config-file items so "no v1 file" skips the item rather than falling
back to the schema default `{}`.
- Type tightening: `MigrationItem.targetKey` and `PreparedData.targetKey`
narrowed from `string` to `BootConfigKey`; all 6 `as BootConfigKey`
assertions deleted. This removes a silent "regen drops the key" failure
mode where the assertions previously suppressed compile errors.
- Generator updates: `generate-boot-config.js` gains a
`MANUAL_BOOT_CONFIG_ITEMS` list that flows through the same sort/emit
pipeline as classification-derived items, keeping `bootConfigSchemas.ts`
as a fully auto-generated single-interface file (no manual sections, no
declaration merging, no preservation logic). `generate-migration.js`
tightens the 3 BootConfig mapping arrays to `targetKey: BootConfigKey`.
- Tests: new `LegacyHomeConfigReader.test.ts` covers 9 scenarios (7 null
returns + 2 populated records); new `BootConfigMigrator.test.ts` covers
the configfile source end-to-end plus a redux-source regression.
- Docs: per-migrator `README-BootConfigMigrator.md` created per the
convention in `v2-migration-guide.md`. `v2-migration-guide.md`,
`boot-config-overview.md`, `boot-config-schema-guide.md`, and
`src/main/data/migration/v2/README.md` updated to mention
BootConfigMigrator, `LegacyHomeConfigReader`, and the 'configfile'
source kind. The AppImage/Windows-portable `executablePath`
normalization gap is documented as a known limitation that must be
addressed in the follow-up read-side PR.
Explicitly out of scope:
- Rewiring `initAppDataDir()` / `updateAppDataConfig()` to read/write
BootConfig — the v1 home config file keeps working unchanged.
- Adding a 'configfile' category to classification.json — the
data-classify toolchain isn't modelled for file sources yet.
- Deleting the v1 home config file post-migration — left intact.
Verified end-to-end on a real v1 config.json: the migrator produced
`"app.user_data_path": { [executablePath]: dataPath }` in
`~/.cherrystudio/boot-config.json`, verbatim-preserving the v1 data.
Signed-off-by: fullex <0xfullex@gmail.com>
|
||
|
|
b618856b71 |
feat(shared): add BootConfig schema and types
Add shared type definitions for the file-based boot configuration system: - BootConfigSchema interface with default values - BootConfigKey type for type-safe key access - BootConfigPreferenceKeys mapped type for unified preference integration Signed-off-by: fullex <0xfullex@gmail.com> |