agent: code-atomiser-fix — migrate Tauri app_data_dir on bundle-identifier change

Commit 14313cf changed the Tauri bundle identifier from
uk.co.corbel.magnotia to consulting.corbel.lumotia. Tauri 2 keys both
app_data_dir and the webview's data store (localStorage, IndexedDB,
cookies, service worker, cache) plus all Tauri-plugin state files
(window-state geometry, autostart enable flag) by the identifier.
Without an explicit migration, every user's webview state was
silently orphaned on first launch under the new identifier; the
JS-side migrateLocalStorageKey helper introduced in 1608109 ran
against an empty store and no-op'd.

Fix: in the Tauri setup hook, before webview navigation, resolve the
legacy app_data_dir under the OLD bundle identifier and recursively
copy it to the new identifier's app_data_dir via an atomic staging
rename. Idempotent: legacy preserved as a backup; if both paths
exist post-rename, a warning is logged and the new path is used
unchanged.

Regression tests cover the migrate-only, both-exist, no-legacy, and
idempotent (run twice) cases against synthetic legacy/new paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 14:35:42 +01:00
parent 6ca94cbff0
commit 3f4e5cc9a4
3 changed files with 400 additions and 1 deletions

View File

@@ -1,4 +1,5 @@
mod commands;
mod tauri_app_data_migration;
// System tray uses Tauri's `tray-icon` feature which is desktop-only.
// Android has no tray surface — drop the module entirely on that target.
#[cfg(not(target_os = "android"))]
@@ -226,6 +227,65 @@ pub fn run() {
builder
.setup(|app| {
// Tauri 2 keys both `app_data_dir` and the webview's data
// store (localStorage, IndexedDB, cookies, service worker
// storage, cache) plus all Tauri plugin state files
// (window-state geometry, autostart enable flag) by the
// bundle identifier. Commit `14313cf` renamed the bundle
// from `uk.co.corbel.magnotia` to `consulting.corbel.lumotia`,
// which would silently orphan every existing user's
// webview state under the new identifier. Migrate the
// legacy identifier-keyed app_data_dir first thing in
// setup, before the webview loads its first page and
// touches the on-disk store. Failure is logged but does
// NOT block startup: the hand-rolled `paths.rs` migration
// below covers the user's transcripts and models, so a
// total failure here only loses webview-keyed state.
//
// This is a separate concern from the
// `~/.local/share/magnotia` → `~/.local/share/lumotia`
// migration just below — that one lives under a
// hand-rolled path the app picks itself; this one lives
// under Tauri's identifier-keyed convention.
let t_app_data = Instant::now();
match app.path().app_data_dir() {
Ok(new_app_data) => {
use crate::tauri_app_data_migration::{
legacy_tauri_app_data_dir, migrate_tauri_app_data_dir_with_paths,
AppDataMigrationStatus,
};
if let Some(legacy) = legacy_tauri_app_data_dir() {
match migrate_tauri_app_data_dir_with_paths(&legacy, &new_app_data) {
AppDataMigrationStatus::Migrated { old, new } => {
tracing::info!(
target: "lumotia_startup",
elapsed_ms = t_app_data.elapsed().as_millis(),
old = %old.display(),
new = %new.display(),
"migrated Tauri app_data_dir from legacy bundle identifier; legacy preserved as backup"
);
}
AppDataMigrationStatus::BothExistLegacyPreserved { old, new } => {
tracing::warn!(
target: "lumotia_startup",
old = %old.display(),
new = %new.display(),
"both legacy and new Tauri app_data_dir exist; using new, legacy preserved"
);
}
AppDataMigrationStatus::NoLegacyFound => {}
}
}
}
Err(e) => {
tracing::warn!(
target: "lumotia_startup",
error = %e,
"could not resolve Tauri app_data_dir; skipping bundle-identifier migration"
);
}
}
// One-shot legacy data-dir migration: rename ~/.local/share/magnotia
// (and macOS/Windows equivalents) to the convention-preserving
// lumotia path on first launch after the rebrand. Idempotent —