mirror of
https://github.com/different-ai/openwork
synced 2026-04-26 01:25:10 +02:00
feat(desktop): migration engine for Tauri → Electron (draft, do not merge) (#1523)
* feat(desktop): migration engine for Tauri → Electron handoff
Implements the infrastructure needed to move users from Tauri to Electron
without losing state or Launchpad/Dock presence. Zero user impact on its
own — this becomes live only when paired with a final Tauri release that
calls the snapshot writer and installs Electron.
What lands here:
- Unify app identity with Tauri. electron-builder appId goes from
com.differentai.openwork.electron → com.differentai.openwork so macOS
reuses the same bundle identifier (no duplicate Dock icon, no new
Gatekeeper prompt, same TCC permissions).
- Unify userData path. Electron's app.setPath("userData", ...) now points
at the exact folder Tauri uses (~/Library/Application Support/com.differentai.openwork
on macOS, %APPDATA%/com.differentai.openwork on Windows, ~/.config/
com.differentai.openwork on Linux). An OPENWORK_ELECTRON_USERDATA env
var override is available for dogfooders who want isolation.
- Filename compat. On first launch, Electron copies Tauri's
openwork-workspaces.json → workspace-state.json (leaves the legacy
file in place for rollback safety).
- electron-updater wiring. New deps + IPC handlers
(openwork:updater:check / download / installAndRestart). Packaged-only.
Publish config points at the same GitHub release as Tauri.
- Migration snapshot plumbing.
* Tauri Rust command `write_migration_snapshot` serializes an
allowlist of localStorage keys to app_data_dir/migration-snapshot.v1.json.
* `apps/app/src/app/lib/migration.ts` has matching
writeMigrationSnapshotFromTauri() / ingestMigrationSnapshotOnElectronBoot().
* Scope: workspace list + selection only
(openwork.react.activeWorkspace, .sessionByWorkspace,
openwork.server.list/active/urlOverride/token). Everything else is
cheap to redo.
* Electron main exposes openwork:migration:read / ack IPC; preload
bridges them under window.__OPENWORK_ELECTRON__.migration.
* desktop-runtime-boot.ts ingests the snapshot once on first launch,
hydrates empty localStorage keys, then acks.
- Updated prds/electron-migration-plan.md with the localStorage scope
decision and the remaining work (last Tauri release ships the UI
prompt + installer downloader).
Verified: `pnpm --filter @openwork/app typecheck` ✓,
`pnpm --filter @openwork/desktop build:electron` ✓, `cargo check`
against src-tauri ✓.
* fix(desktop): emit relative asset paths for Electron packaged builds
Packaged Electron loads index.html via file:// (Contents/Resources/app-dist/
index.html inside the .app bundle), but Vite was building with the default
base: '/' which resolves /assets/*.js to the filesystem root. Result: a
working dev experience (Vite dev server on localhost:5173) and a broken
packaged .app that renders an empty <div id="root"></div>.
The Tauri shell doesn't hit this because Tauri serves the built HTML
through its own tauri:// protocol which rewrites paths. Electron's
file:// loader has no such rewriter.
Fix: electron-build.mjs sets OPENWORK_ELECTRON_BUILD=1 when invoking
vite build, and vite.config.ts flips base to './' only when that env
var is set. Dev server and Tauri builds unchanged.
Discovered while manually exercising the Electron prod-build migration
flow (packaged app launch, snapshot ingest, idempotency, updater IPC).
Latent since PR #1522 landed the packaged build path; never caught
because dogfood ran via dev:electron which uses the Vite dev server.
---------
Co-authored-by: Benjamin Shafii <benjamin@openworklabs.com>
This commit is contained in:
61
apps/desktop/src-tauri/src/commands/migration.rs
Normal file
61
apps/desktop/src-tauri/src/commands/migration.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
const MIGRATION_SNAPSHOT_FILENAME: &str = "migration-snapshot.v1.json";
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct MigrationSnapshotPayload {
|
||||
pub version: u32,
|
||||
#[serde(rename = "writtenAt")]
|
||||
pub written_at: Option<i64>,
|
||||
pub source: Option<String>,
|
||||
pub keys: HashMap<String, String>,
|
||||
}
|
||||
|
||||
fn migration_snapshot_path(app: &AppHandle) -> Result<PathBuf, String> {
|
||||
let data_dir = app
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.map_err(|e| format!("Failed to resolve app_data_dir: {e}"))?;
|
||||
fs::create_dir_all(&data_dir)
|
||||
.map_err(|e| format!("Failed to create app_data_dir: {e}"))?;
|
||||
Ok(data_dir.join(MIGRATION_SNAPSHOT_FILENAME))
|
||||
}
|
||||
|
||||
/// Snapshot workspace-related localStorage keys into app_data_dir so the
|
||||
/// next-launch Electron shell can hydrate them. Called by the last Tauri
|
||||
/// release right before it kicks off the Electron installer.
|
||||
#[tauri::command]
|
||||
pub fn write_migration_snapshot(
|
||||
app: AppHandle,
|
||||
snapshot: MigrationSnapshotPayload,
|
||||
) -> Result<(), String> {
|
||||
if snapshot.version != 1 {
|
||||
return Err(format!(
|
||||
"Unsupported migration snapshot version: {}",
|
||||
snapshot.version
|
||||
));
|
||||
}
|
||||
|
||||
let path = migration_snapshot_path(&app)?;
|
||||
let serialized = serde_json::json!({
|
||||
"version": snapshot.version,
|
||||
"writtenAt": snapshot.written_at,
|
||||
"source": snapshot.source.unwrap_or_else(|| "tauri".to_string()),
|
||||
"keys": snapshot.keys,
|
||||
});
|
||||
let contents = serde_json::to_string_pretty(&serialized)
|
||||
.map_err(|e| format!("Failed to serialize snapshot: {e}"))?;
|
||||
fs::write(&path, contents).map_err(|e| format!("Failed to write snapshot: {e}"))?;
|
||||
|
||||
println!(
|
||||
"[migration] wrote {} key(s) to {}",
|
||||
snapshot.keys.len(),
|
||||
path.display()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -2,6 +2,7 @@ pub mod command_files;
|
||||
pub mod config;
|
||||
pub mod desktop_bootstrap;
|
||||
pub mod engine;
|
||||
pub mod migration;
|
||||
pub mod misc;
|
||||
pub mod opencode_router;
|
||||
pub mod openwork_server;
|
||||
|
||||
@@ -24,6 +24,7 @@ use commands::desktop_bootstrap::{get_desktop_bootstrap_config, set_desktop_boot
|
||||
use commands::engine::{
|
||||
engine_doctor, engine_info, engine_install, engine_restart, engine_start, engine_stop,
|
||||
};
|
||||
use commands::migration::write_migration_snapshot;
|
||||
use commands::misc::{
|
||||
app_build_info, nuke_openwork_and_opencode_config_and_exit, opencode_mcp_auth,
|
||||
reset_opencode_cache, reset_openwork_state,
|
||||
@@ -208,6 +209,7 @@ pub fn run() {
|
||||
get_desktop_bootstrap_config,
|
||||
set_desktop_bootstrap_config,
|
||||
updater_environment,
|
||||
write_migration_snapshot,
|
||||
app_build_info,
|
||||
nuke_openwork_and_opencode_config_and_exit,
|
||||
reset_openwork_state,
|
||||
|
||||
Reference in New Issue
Block a user