* 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>
* refactor(app): remove hide_main_window function and update macOS window close behavior
Eliminate the hide_main_window function to streamline window management. Update the macOS behavior to exit the application when the main window is closed, aligning it with the behavior on Windows and Linux.
* surface openwork as main focused window onDock click
* refactor(app): remove hide_main_window function and update macOS window close behavior
Eliminate the hide_main_window function to streamline window management. Update the macOS behavior to exit the application when the main window is closed, aligning it with the behavior on Windows and Linux.
* surface openwork as main focused window onDock click
* add pre-baked microsandbox image
Bake openwork, openwork-server, and the pinned opencode binary into a single Docker image so micro-sandbox remote-connect smoke tests can boot quickly and be verified with curl and container health checks.
* add Rust microsandbox example
Add a standalone microsandbox SDK example that boots the OpenWork image, validates remote-connect endpoints, and streams sandbox logs so backend-only sandbox behavior can be exercised without Docker.
* exclude Rust example build output
Keep the standalone microsandbox example in git, but drop generated Cargo target artifacts so the branch only contains source, docs, and lockfile.
* test
* add microsandbox feature flag for sandbox creation
Made-with: Cursor
* refactor sandbox mode isolation
Made-with: Cursor
Shift startup/loading control to TS with explicit boot phases, cached->live sidebar transitions, and startup trace markers while reducing Rust-side blocking in workspace and orchestrator status paths to improve macOS responsiveness during boot and workspace switching.
Made-with: Cursor
Break assistant text into stable blocks so streaming stays responsive and tool-created files surface as openable transcript cards instead of getting buried in prose.
* Isolate OpenCode dev state and clarify destructive reset
Route dev-mode OpenCode state into a dedicated openwork-dev-data home so local development never touches the default user path. Move the destructive reset to Debug and make it wipe only the current mode's OpenWork and OpenCode state with an explicit irreversible warning.
* Unify managed OpenCode state paths
Keep the supported desktop/orchestrator flow on a single OpenCode state root per mode instead of creating per-workspace directories. This makes dev and production resets target the right shared paths without changing the standard non-dev OpenCode data locations.
---------
Co-authored-by: src-opn <src-opn@users.noreply.github.com>
* fix(app): decouple workspace selection from runtime state
Keep desktop workspace selection independent from backend activation so switching workers only reconnects when an action actually needs that runtime. Persist per-workspace local OpenWork server ports so long-lived local links stay stable without relying on a predictable default port.
* fix(app): resolve rebased workspace runtime references
* fix(test): auth opencode serve regression scripts
* docs(app): clarify workspace runtime and port behavior
---------
Co-authored-by: Omar McAdam <omar@OpenWork-Studio.localdomain>
Co-authored-by: Omar McAdam <omar@OpenWork-Studio.local>