diff --git a/crates/app-tauri/src/server.rs b/crates/app-tauri/src/server.rs index 1fc8192..24d3be5 100644 --- a/crates/app-tauri/src/server.rs +++ b/crates/app-tauri/src/server.rs @@ -68,6 +68,10 @@ pub fn run_from_args(args: Vec) -> ExitCode { let state = Arc::new(ServerState::new(config.clone())); eprintln!("IdeA pairing code: {}", state.pairing_code()); + eprintln!( + "idea --serve: app data dir = {}", + config.app_data_dir.display() + ); eprintln!("IdeA server listening on {}", config.listen); match tokio::runtime::Builder::new_multi_thread() @@ -1802,10 +1806,10 @@ fn default_app_data_dir() -> PathBuf { return PathBuf::from(path); } if let Some(path) = env::var_os("XDG_DATA_HOME") { - return PathBuf::from(path).join("IdeA"); + return PathBuf::from(path).join("app.idea.ide"); } if let Some(home) = env::var_os("HOME") { - return PathBuf::from(home).join(".local/share/IdeA"); + return PathBuf::from(home).join(".local/share/app.idea.ide"); } env::current_dir() .unwrap_or_else(|_| PathBuf::from(".")) @@ -2300,6 +2304,35 @@ mod tests { use http::header::HeaderName; use std::time::Duration; + static ENV_LOCK: Mutex<()> = Mutex::new(()); + + struct EnvVarGuard { + saved: Vec<(&'static str, Option)>, + } + + impl EnvVarGuard { + fn new(keys: &[&'static str]) -> Self { + Self { + saved: keys + .iter() + .map(|key| (*key, std::env::var_os(key))) + .collect(), + } + } + } + + impl Drop for EnvVarGuard { + fn drop(&mut self) { + for (key, value) in self.saved.iter().rev() { + if let Some(value) = value { + std::env::set_var(key, value); + } else { + std::env::remove_var(key); + } + } + } + } + #[derive(Default)] struct RecordingSecurityLogger { events: Mutex>, @@ -2341,6 +2374,30 @@ mod tests { } } + #[test] + fn default_app_data_dir_uses_tauri_identifier_with_env_precedence() { + let _lock = ENV_LOCK.lock().unwrap(); + let _guard = EnvVarGuard::new(&["IDEA_APP_DATA_DIR", "XDG_DATA_HOME", "HOME"]); + let root = std::env::temp_dir().join(format!("idea-app-data-env-{}", Uuid::new_v4())); + let home = root.join("home"); + let xdg = root.join("xdg"); + let override_dir = root.join("override"); + + std::env::remove_var("IDEA_APP_DATA_DIR"); + std::env::remove_var("XDG_DATA_HOME"); + std::env::set_var("HOME", &home); + assert_eq!( + default_app_data_dir(), + home.join(".local/share/app.idea.ide") + ); + + std::env::set_var("XDG_DATA_HOME", &xdg); + assert_eq!(default_app_data_dir(), xdg.join("app.idea.ide")); + + std::env::set_var("IDEA_APP_DATA_DIR", &override_dir); + assert_eq!(default_app_data_dir(), override_dir); + } + fn state() -> Arc { Arc::new(ServerState::new_for_test(test_config(), "PAIR1234")) } diff --git a/docs/server-client-mode-remote.md b/docs/server-client-mode-remote.md index 03504b9..58e7a5b 100644 --- a/docs/server-client-mode-remote.md +++ b/docs/server-client-mode-remote.md @@ -5,14 +5,35 @@ build must be available as a web root containing `index.html`. ## Local Development +The web build **must** be produced with the `http` transport, otherwise the +served `dist/` is a desktop (Tauri) build that fails in a plain browser with +`window.__TAURI_INTERNALS__ is undefined`. The frontend uses **npm**, not pnpm: + ```bash -pnpm --dir frontend build -idea --serve --web-root frontend/dist +cd frontend && VITE_TRANSPORT=http npx vite build && cd .. +idea --serve --web-root frontend/dist --app-data-dir "$HOME/.local/share/app.idea.ide" ``` The default local listener is `127.0.0.1:17373`. Loopback development may use plain HTTP; the session cookie is still `HttpOnly` and `SameSite=Strict`. +### App data directory (must match the desktop app) + +`idea --serve` reads/writes the same on-disk data as the desktop app +(projects, profiles, templates). It resolves the app-data directory in this +order: + +1. `--app-data-dir PATH` +2. `IDEA_APP_DATA_DIR` +3. platform default for the Tauri identifier `app.idea.ide` + (`$XDG_DATA_HOME/app.idea.ide`, else `$HOME/.local/share/app.idea.ide`) + +The resolved path is printed at startup (`idea --serve: app data dir = …`). + +> **Do not run the desktop app and `idea --serve` on the same app-data +> directory at the same time.** There is no inter-process lock yet, so two +> concurrent writers can corrupt state. Close the desktop app while serving. + ## Packaged Artifact The release artifact is still a single IdeA binary/AppImage. Packaging must copy