wraith/src-tauri/src/workspace/mod.rs
Vantz Stockwell 0cd4cc0f64 feat: Phase 5 complete — themes, editor, shortcuts, workspace, settings
Theme service: 7 built-in themes seeded from Rust, ThemePicker
loads from backend. Workspace service: save/load snapshots, crash
recovery detection. SettingsModal: full port with Tauri invoke.
CommandPalette, HostKeyDialog, ConnectionEditDialog all ported.

CodeMirror editor: inline above terminal, reads/writes via SFTP.
Full keyboard shortcuts: Ctrl+K/W/Tab/Shift+Tab/1-9/B/F.
Terminal search bar via Ctrl+F (SearchAddon).
Tab badges: protocol dots, ROOT warning, environment pills.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 16:36:06 -04:00

83 lines
3.1 KiB
Rust

use serde::{Deserialize, Serialize};
use crate::settings::SettingsService;
// ── domain types ─────────────────────────────────────────────────────────────
/// A single open tab that should be restored on next launch.
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct WorkspaceTab {
pub connection_id: i64,
pub protocol: String,
pub position: i32,
}
/// The full workspace state persisted between sessions.
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct WorkspaceSnapshot {
pub tabs: Vec<WorkspaceTab>,
}
// ── service ───────────────────────────────────────────────────────────────────
const SNAPSHOT_KEY: &str = "workspace_snapshot";
const CLEAN_SHUTDOWN_KEY: &str = "clean_shutdown";
pub struct WorkspaceService {
settings: SettingsService,
}
impl WorkspaceService {
pub fn new(settings: SettingsService) -> Self {
Self { settings }
}
/// Serialize `snapshot` to JSON and persist it via the settings store.
pub fn save(&self, snapshot: &WorkspaceSnapshot) -> Result<(), String> {
let json = serde_json::to_string(snapshot)
.map_err(|e| format!("workspace::save: failed to serialize snapshot: {e}"))?;
self.settings.set(SNAPSHOT_KEY, &json)
}
/// Load and deserialize the last saved workspace snapshot.
///
/// Returns `None` if no snapshot has been saved yet or if deserialization
/// fails (treated as a fresh start rather than a hard error).
pub fn load(&self) -> Option<WorkspaceSnapshot> {
let json = self.settings.get(SNAPSHOT_KEY)?;
serde_json::from_str(&json)
.map_err(|e| eprintln!("workspace::load: failed to deserialize snapshot: {e}"))
.ok()
}
/// Record that the application is shutting down cleanly.
///
/// Call this just before the Tauri window closes normally. On the next
/// startup, `was_clean_shutdown()` will return `true` and the crash-
/// recovery path can be skipped.
pub fn mark_clean_shutdown(&self) -> Result<(), String> {
self.settings.set(CLEAN_SHUTDOWN_KEY, "true")
}
/// Returns `true` if the last session ended with a clean shutdown.
///
/// Returns `false` (crash / dirty shutdown) when the key is absent, empty,
/// or set to any value other than `"true"`.
pub fn was_clean_shutdown(&self) -> bool {
self.settings
.get(CLEAN_SHUTDOWN_KEY)
.map(|v| v == "true")
.unwrap_or(false)
}
/// Remove the clean-shutdown flag.
///
/// Call this at startup — immediately after reading `was_clean_shutdown()`.
/// The flag is only valid for the single launch following the shutdown that
/// wrote it; clearing it ensures a subsequent crash is detected correctly.
pub fn clear_clean_shutdown(&self) -> Result<(), String> {
self.settings.delete(CLEAN_SHUTDOWN_KEY)
}
}