feat(db): in-app recovery screen with upgrade button when DB version is too new (#4575)

* feat(db): in-app recovery screen with upgrade button when DB version is too new

When the SQLite user_version is newer than the app supports (SCHEMA_VERSION),
Database::init() fails and previously dead-ended in a native Retry/Exit dialog
(Retry just fails again). The app now boots a dedicated recovery screen instead.

- Detect the recoverable "version too new" case and surface it via init_status
  (kind="db_version_too_new" + db_version/supported_version); force-show the main
  window and skip normal AppState boot (recovery commands need only AppHandle).
- The recovery screen first checks for an available update:
  - update available  -> "Upgrade app" runs the updater (download + install +
    restart) with a download progress bar.
  - no update (already latest) -> warns that the DB is too new even for the latest
    build (likely a third-party client), so upgrading cannot help.
- install_update_and_restart now emits `update-download-progress` events; new
  `check_app_update_available` command.
- i18n: en / ja / zh / zh-TW.

Verified: pnpm typecheck + format:check + test:unit (351) green; cross-model
review of Rust correctness and recovery-mode safety (no AppState panic).

* fix(db): pre-check too-new DB before schema writes; exit on close in recovery mode

Addresses review feedback on the too-new-DB recovery flow.

- P1: stored_user_version_exceeds_supported() is now checked BEFORE
  Database::init(), so create_tables()'s DDL (incl. the unconditional
  DROP INDEX/DROP TABLE IF EXISTS failover_queue) never runs against a
  database whose user_version we cannot understand. The earlier
  post-init-failure recovery branch is removed; the pre-check is the
  single authoritative guard, so "don't write to a DB we can't read"
  now holds from the very first DB access.

- P2: the window CloseRequested handler now detects recovery mode
  (init_error kind = db_version_too_new) and exits the app instead of
  honoring minimize_to_tray_on_close. Recovery mode returns before the
  tray is created, so hiding the window would leave the app running with
  no tray to bring it back; native-close now quits cleanly.

Verified: cargo fmt --check clean; cross-model Rust review of both fixes
(compile-correctness + no normal-startup/close regression).
This commit is contained in:
SaladDay
2026-06-24 15:07:42 +08:00
committed by GitHub
parent 55abd1822c
commit edeee25fae
10 changed files with 497 additions and 3 deletions
+17
View File
@@ -1610,6 +1610,23 @@
"tip2": "• Extractor function runs in sandbox environment, supports ES2020+ syntax",
"tip3": "• Entire config must be wrapped in () to form object literal expression"
},
"dbUpgrade": {
"title": "Database version is too new",
"description": "This database was created by a newer version of CC Switch. Upgrade the app to continue. Upgrading will not delete your data.",
"versionInfo": "Database v{{db}} · App supports v{{supported}}",
"dbPath": "Database file",
"checking": "Checking for available updates…",
"updateAvailable": "Version v{{version}} is available; upgrading will let you continue.",
"incompatibleTitle": "Upgrading won't fix this",
"incompatibleDescription": "You are already on the latest version, but the database (v{{db}}) is still newer than this app supports (v{{supported}}). It was likely created by a third-party client or a newer build, so upgrading the official app cannot make it compatible.",
"preparing": "Preparing update…",
"downloading": "Downloading update…",
"upgradeNow": "Upgrade app",
"retry": "Retry upgrade",
"openReleases": "Open releases page",
"openConfigDir": "Open config folder",
"quit": "Quit"
},
"errors": {
"usage_query_failed": "Usage query failed",
"configLoadFailedTitle": "Configuration Load Failed",
+17
View File
@@ -1610,6 +1610,23 @@
"tip2": "• 抽出関数はサンドボックスで実行され、ES2020+ の構文を使えます",
"tip3": "• 全体を () で囲み、オブジェクトリテラル式にしてください"
},
"dbUpgrade": {
"title": "データベースのバージョンが新しすぎます",
"description": "このデータベースは新しいバージョンの CC Switch で作成されています。続行するにはアプリを更新してください。更新してもデータは削除されません。",
"versionInfo": "データベース v{{db}} · アプリ対応 v{{supported}}",
"dbPath": "データベースファイル",
"checking": "利用可能な更新を確認中…",
"updateAvailable": "新しいバージョン v{{version}} が利用可能です。更新すると続行できます。",
"incompatibleTitle": "更新しても解決できません",
"incompatibleDescription": "すでに最新バージョンですが、データベース(v{{db}})はこのアプリの対応バージョン(v{{supported}})より新しいままです。サードパーティ製クライアントまたはより新しいビルドで作成された可能性があり、公式アプリを更新しても互換性は得られません。",
"preparing": "更新を準備中…",
"downloading": "更新をダウンロード中…",
"upgradeNow": "アプリを更新",
"retry": "更新を再試行",
"openReleases": "リリースページを開く",
"openConfigDir": "設定フォルダを開く",
"quit": "終了"
},
"errors": {
"usage_query_failed": "利用状況の取得に失敗しました",
"configLoadFailedTitle": "設定の読み込みに失敗しました",
+17
View File
@@ -1582,6 +1582,23 @@
"tip2": "• extractor 函式在沙盒環境中執行,支援 ES2020+ 語法",
"tip3": "• 整個設定必須用 () 包覆,形成物件實字運算式"
},
"dbUpgrade": {
"title": "資料庫版本過新",
"description": "目前資料庫由較新版本的 CC Switch 建立,需要升級應用後才能繼續使用。升級不會刪除你的資料。",
"versionInfo": "資料庫版本 v{{db}} · 應用支援 v{{supported}}",
"dbPath": "資料庫檔案",
"checking": "正在檢查可用更新…",
"updateAvailable": "發現新版本 v{{version}},升級後即可繼續使用。",
"incompatibleTitle": "升級也無法解決",
"incompatibleDescription": "你已是最新版本,但資料庫版本(v{{db}})仍高於本應用支援的版本(v{{supported}})。該資料庫可能由第三方用戶端或更高版本建立,升級目前官方應用也無法相容。",
"preparing": "正在準備更新…",
"downloading": "正在下載更新…",
"upgradeNow": "升級應用",
"retry": "重試升級",
"openReleases": "開啟發佈頁",
"openConfigDir": "開啟設定目錄",
"quit": "結束"
},
"errors": {
"usage_query_failed": "用量查詢失敗",
"configLoadFailedTitle": "設定載入失敗",
+17
View File
@@ -1610,6 +1610,23 @@
"tip2": "• extractor 函数在沙箱环境中执行,支持 ES2020+ 语法",
"tip3": "• 整个配置必须用 () 包裹,形成对象字面量表达式"
},
"dbUpgrade": {
"title": "数据库版本过新",
"description": "当前数据库由更新版本的 CC Switch 创建,需要升级应用后才能继续使用。升级不会删除你的数据。",
"versionInfo": "数据库版本 v{{db}} · 应用支持 v{{supported}}",
"dbPath": "数据库文件",
"checking": "正在检查可用更新…",
"updateAvailable": "发现新版本 v{{version}},升级后即可继续使用。",
"incompatibleTitle": "升级也无法解决",
"incompatibleDescription": "你已是最新版本,但数据库版本(v{{db}})仍高于本应用支持的版本(v{{supported}})。该数据库可能由第三方客户端或更高版本创建,升级当前官方应用也无法兼容。",
"preparing": "正在准备更新…",
"downloading": "正在下载更新…",
"upgradeNow": "升级应用",
"retry": "重试升级",
"openReleases": "打开发布页",
"openConfigDir": "打开配置目录",
"quit": "退出"
},
"errors": {
"usage_query_failed": "用量查询失败",
"configLoadFailedTitle": "配置加载失败",