Skip to main content

Mountain/Binary/Main/
AppLifecycle.rs

1//! # AppLifecycle (Binary/Main)
2//!
3//! ## RESPONSIBILITIES
4//!
5//! Application lifecycle management for the Tauri application setup and
6//! initialization. This module handles the complete setup process during the
7//! Tauri setup hook, including tray initialization, command registration, IPC
8//! server setup, window creation, environment configuration, and async service
9//! initialization.
10//!
11//! ## ARCHITECTURAL ROLE
12//!
13//! The AppLifecycle module is the **initialization layer** in Mountain's
14//! architecture:
15//!
16//! ```text
17//! Tauri Builder Setup ──► AppLifecycle::AppLifecycleSetup()
18//!                              │
19//!                              ├─► Tray Initialization
20//!                              ├─► Command Registration
21//!                              ├─► IPC Server Setup
22//!                              ├─► Window Building
23//!                              ├─► Environment Setup
24//!                              ├─► Runtime Setup
25//!                              └─► Async Service Initialization
26//! ```
27//!
28//! ## KEY COMPONENTS
29//!
30//! - **AppLifecycleSetup()**: Main setup function orchestrating all
31//!   initialization
32//! - **Tray Initialization**: System tray icon with Dark/Light mode support
33//! - **Command Registration**: Native command registration with application
34//!   state
35//! - **IPC Server**: Mountain IPC server for frontend-backend communication
36//! - **Window Building**: Main application window configuration
37//! - **MountainEnvironment**: Environment context for application services
38//! - **ApplicationRunTime**: Runtime context with scheduler and environment
39//! - **Status Reporter**: IPC status reporting initialization
40//! - **Advanced Features**: Advanced IPC features initialization
41//! - **Wind Sync**: Wind advanced sync initialization
42//! - **Async Initialization**: Post-setup async service initialization
43//!
44//! ## ERROR HANDLING
45//!
46//! Returns `Result<(), Box<dyn std::error::Error>>` for setup errors.
47//! Non-critical failures are logged but don't prevent application startup.
48//! Critical failures are propagated to prevent incomplete startup.
49//!
50//! ## LOGGING
51//!
52//! Comprehensive logging at INFO level for major setup steps,
53//! DEBUG level for detailed processing, and ERROR for failures.
54//! All logs are prefixed with `[Lifecycle] [ComponentName]`.
55//!
56//! ## PERFORMANCE CONSIDERATIONS
57//!
58//! - Async initialization spawned after main setup to avoid blocking
59//! - Services initialized only when needed
60//! - Clone operations minimized for Arc-wrapped shared state
61//!
62//! ## TODO
63//! - [ ] Add setup progress tracking
64//! - [ ] Implement setup timeout handling
65//! - [ ] Add setup rollback mechanism on failure
66
67use std::sync::Arc;
68
69use tauri::Manager;
70use Echo::Scheduler::Scheduler::Scheduler;
71
72use crate::dev_log;
73use crate::{
74	// Crate root imports
75	ApplicationState::ApplicationState,
76	// Binary submodule imports
77	Binary::Build::WindowBuild::WindowBuild as WindowBuildFn,
78	Binary::Extension::ExtensionPopulate::ExtensionPopulate as ExtensionPopulateFn,
79	Binary::Extension::ScanPathConfigure::ScanPathConfigure as ScanPathConfigureFn,
80	Binary::Register::AdvancedFeaturesRegister::AdvancedFeaturesRegister as AdvancedFeaturesRegisterFn,
81	Binary::Register::CommandRegister::CommandRegister as CommandRegisterFn,
82	Binary::Register::IPCServerRegister::IPCServerRegister as IPCServerRegisterFn,
83	Binary::Register::StatusReporterRegister::StatusReporterRegister as StatusReporterRegisterFn,
84	Binary::Register::WindSyncRegister::WindSyncRegister as WindSyncRegisterFn,
85	Binary::Service::CocoonStart::CocoonStart as CocoonStartFn,
86	Binary::Service::ConfigurationInitialize::ConfigurationInitialize as ConfigurationInitializeFn,
87	Binary::Service::VineStart::VineStart as VineStartFn,
88	Binary::Tray::EnableTray as EnableTrayFn,
89	Environment::MountainEnvironment::MountainEnvironment,
90	RunTime::ApplicationRunTime::ApplicationRunTime,
91};
92
93/// Logs a checkpoint message at TRACE level.
94macro_rules! TraceStep {
95	($($arg:tt)*) => {{
96		dev_log!("lifecycle", $($arg)*);
97	}};
98}
99
100/// Sets up the application lifecycle during Tauri initialization.
101///
102/// This function coordinates all setup operations:
103/// 1. System tray initialization
104/// 2. Native command registration
105/// 3. IPC server initialization
106/// 4. Main window creation
107/// 5. Mountain environment setup
108/// 6. Application runtime setup
109/// 7. Status reporter initialization
110/// 8. Advanced features initialization
111/// 9. Wind advanced sync initialization
112/// 10. Async post-setup initialization
113///
114/// # Parameters
115///
116/// * `app` - Mutable reference to Tauri App instance
117/// * `app_handle` - Cloned Tauri AppHandle for async operations
118/// * `localhost_url` - URL for the development server
119/// * `scheduler` - Arc-wrapped Echo Scheduler
120/// * `app_state` - Application state clone
121///
122/// # Returns
123///
124/// `Result<(), Box<dyn std::error::Error>>` - Ok on success, Err on critical
125/// failure
126pub fn AppLifecycleSetup(
127	app:&mut tauri::App,
128	app_handle:tauri::AppHandle,
129	localhost_url:String,
130	scheduler:Arc<Scheduler>,
131	app_state:Arc<ApplicationState>,
132) -> Result<(), Box<dyn std::error::Error>> {
133	dev_log!("lifecycle", "[Lifecycle] [Setup] Setup hook started.");
134	dev_log!("lifecycle", "[Lifecycle] [Setup] LocalhostUrl={}", localhost_url);
135
136	let app_handle_for_setup = app_handle.clone();
137	TraceStep!("[Lifecycle] [Setup] AppHandle acquired.");
138
139	// -------------------------------------------------------------------------
140	// [UI] [Tray] Initialize System Tray
141	// -------------------------------------------------------------------------
142	dev_log!("lifecycle", "[UI] [Tray] Initializing system tray...");
143	if let Err(Error) = EnableTrayFn::enable_tray(app) {
144		dev_log!("lifecycle", "error: [UI] [Tray] Failed to enable tray: {}", Error);
145	}
146
147	// -------------------------------------------------------------------------
148	// [Lifecycle] [Commands] Register native commands
149	// -------------------------------------------------------------------------
150	dev_log!("lifecycle", "[Lifecycle] [Commands] Registering native commands...");
151	if let Err(e) = CommandRegisterFn(&app_handle_for_setup, &app_state) {
152		dev_log!("lifecycle", "error: [Lifecycle] [Commands] Failed to register commands: {}", e);
153	}
154	dev_log!("lifecycle", "[Lifecycle] [Commands] Native commands registered.");
155
156	// -------------------------------------------------------------------------
157	// [Lifecycle] [IPC] Initialize IPC Server
158	// -------------------------------------------------------------------------
159	dev_log!("lifecycle", "[Lifecycle] [IPC] Initializing Mountain IPC Server...");
160	if let Err(e) = IPCServerRegisterFn(&app_handle_for_setup) {
161		dev_log!("lifecycle", "error: [Lifecycle] [IPC] Failed to register IPC server: {}", e);
162	}
163
164	// -------------------------------------------------------------------------
165	// [UI] [Window] Build main window
166	// -------------------------------------------------------------------------
167	dev_log!("lifecycle", "[UI] [Window] Building main window...");
168	let MainWindow = WindowBuildFn(app, localhost_url.clone());
169	dev_log!("lifecycle", "[UI] [Window] Main window ready.");
170
171	#[cfg(debug_assertions)]
172	{
173		dev_log!("lifecycle", "[UI] [Window] Debug build: opening DevTools.");
174		MainWindow.open_devtools();
175	}
176
177	// -------------------------------------------------------------------------
178	// [Backend] [Dirs] Ensure userdata directories exist
179	// -------------------------------------------------------------------------
180	{
181		let PathResolver = app.path();
182		let AppDataDir = PathResolver.app_data_dir().unwrap_or_default();
183		let LogDir = PathResolver.app_log_dir().unwrap_or_default();
184		let HomeDir = PathResolver.home_dir().unwrap_or_default();
185
186		// Set the canonical userdata base so WindServiceHandlers resolves
187		// /User/... paths to the real Tauri app_data_dir (not hardcoded "Land").
188		crate::IPC::WindServiceHandlers::set_userdata_base_dir(AppDataDir.to_string_lossy().to_string());
189
190		// Set the real filesystem root for /Static/Application/ path mapping.
191		// In dev mode, Tauri serves from ../Sky/Target relative to Mountain.
192		// Tauri's resource_dir gives us the frontendDist path.
193		let SkyTargetDir = PathResolver.resource_dir().unwrap_or_else(|_| {
194			// Fallback: dev-only path based on the monorepo layout. Release
195			// builds must rely on Tauri's bundled resource_dir() — if that
196			// fails in prod, something is wrong with the bundle. The cfg
197			// keeps the dev fallback out of release binaries so a broken
198			// bundle fails loudly instead of creating directories outside it.
199			#[cfg(debug_assertions)]
200			{
201				std::env::current_exe()
202					.ok()
203					.and_then(|Exe| Exe.parent().map(|P| P.to_path_buf()))
204					.unwrap_or_default()
205					.join("../../../Sky/Target")
206			}
207			#[cfg(not(debug_assertions))]
208			{
209				std::path::PathBuf::new()
210			}
211		});
212		crate::IPC::WindServiceHandlers::set_static_application_root(SkyTargetDir.to_string_lossy().to_string());
213		dev_log!(
214			"lifecycle",
215			"[Lifecycle] [Dirs] Static application root: {}",
216			SkyTargetDir.display()
217		);
218
219		// Every directory VS Code may stat or readdir during startup
220		let Dirs = [
221			// User profile directories
222			AppDataDir.join("User"),
223			AppDataDir.join("User/globalStorage"),
224			AppDataDir.join("User/workspaceStorage"),
225			AppDataDir.join("User/workspaceStorage/vscode-chat-images"),
226			AppDataDir.join("User/extensions"),
227			AppDataDir.join("User/profiles/__default__profile__"),
228			AppDataDir.join("User/snippets"),
229			AppDataDir.join("User/prompts"),
230			AppDataDir.join("User/caches"),
231			// Configuration cache
232			AppDataDir.join("CachedConfigurations/defaults/__default__profile__-configurationDefaultsOverrides"),
233			// Log directories - VS Code stats {logsPath}/window1/output_{timestamp}
234			LogDir.join("window1"),
235			// System extensions directory - VS Code scans appRoot/../extensions
236			// which resolves to /Static/Application/extensions (mapped to Sky Target).
237			SkyTargetDir.join("Static/Application/extensions"),
238			// Agent directories VS Code probes for (create to avoid stat errors)
239			HomeDir.join(".claude/agents"),
240			HomeDir.join(".copilot/agents"),
241		];
242		for Dir in &Dirs {
243			if let Err(Error) = std::fs::create_dir_all(Dir) {
244				dev_log!(
245					"lifecycle",
246					"warn: [Lifecycle] [Dirs] Failed to create {}: {}",
247					Dir.display(),
248					Error
249				);
250			}
251		}
252
253		// Default empty files VS Code reads on startup
254		let DefaultFiles:&[(&std::path::Path, &str)] = &[
255			(&AppDataDir.join("User/settings.json"), "{}"),
256			(&AppDataDir.join("User/keybindings.json"), "[]"),
257			(&AppDataDir.join("User/tasks.json"), "{}"),
258			(&AppDataDir.join("User/extensions.json"), "[]"),
259			(&AppDataDir.join("User/mcp.json"), "{}"),
260		];
261		for (FilePath, DefaultContent) in DefaultFiles {
262			if !FilePath.exists() {
263				let _ = std::fs::write(FilePath, DefaultContent);
264			}
265		}
266
267		// Set GlobalMementoPath now that we know the real Tauri app data dir
268		if let Ok(mut Path) = app_state.GlobalMementoPath.lock() {
269			*Path = AppDataDir.join("User/globalStorage/global.json");
270			dev_log!("lifecycle", "[Lifecycle] [Dirs] GlobalMementoPath: {}", Path.display());
271		}
272		dev_log!(
273			"lifecycle",
274			"[Lifecycle] [Dirs] Userdata directories ensured at {}",
275			AppDataDir.display()
276		);
277	}
278
279	// -------------------------------------------------------------------------
280	// [Backend] [Env] Mountain environment
281	// -------------------------------------------------------------------------
282	dev_log!("lifecycle", "[Backend] [Env] Creating MountainEnvironment...");
283	let Environment = Arc::new(MountainEnvironment::Create(app_handle_for_setup.clone(), app_state.clone()));
284	dev_log!("lifecycle", "[Backend] [Env] MountainEnvironment ready.");
285
286	// -------------------------------------------------------------------------
287	// [Backend] [Runtime] ApplicationRunTime
288	// -------------------------------------------------------------------------
289	dev_log!("lifecycle", "[Backend] [Runtime] Creating ApplicationRunTime...");
290	let Runtime = Arc::new(ApplicationRunTime::Create(scheduler.clone(), Environment.clone()));
291	app_handle_for_setup.manage(Runtime.clone());
292	dev_log!("lifecycle", "[Backend] [Runtime] ApplicationRunTime managed.");
293
294	// -------------------------------------------------------------------------
295	// [Lifecycle] [IPC] Initialize Status Reporter
296	// -------------------------------------------------------------------------
297	if let Err(e) = StatusReporterRegisterFn(&app_handle_for_setup, Runtime.clone()) {
298		dev_log!(
299			"lifecycle",
300			"error: [Lifecycle] [IPC] Failed to initialize status reporter: {}",
301			e
302		);
303	}
304
305	// -------------------------------------------------------------------------
306	// [Lifecycle] [IPC] Initialize Advanced Features
307	// -------------------------------------------------------------------------
308	if let Err(e) = AdvancedFeaturesRegisterFn(&app_handle_for_setup, Runtime.clone()) {
309		dev_log!(
310			"lifecycle",
311			"error: [Lifecycle] [IPC] Failed to initialize advanced features: {}",
312			e
313		);
314	}
315
316	// -------------------------------------------------------------------------
317	// [Lifecycle] [IPC] Initialize Wind Advanced Sync
318	// -------------------------------------------------------------------------
319	if let Err(e) = WindSyncRegisterFn(&app_handle_for_setup, Runtime.clone()) {
320		dev_log!(
321			"lifecycle",
322			"error: [Lifecycle] [IPC] Failed to initialize wind advanced sync: {}",
323			e
324		);
325	}
326
327	// -------------------------------------------------------------------------
328	// [Lifecycle] [PostSetup] Async initialization work
329	// -------------------------------------------------------------------------
330	let PostSetupAppHandle = app_handle_for_setup.clone();
331	let PostSetupEnvironment = Environment.clone();
332
333	tauri::async_runtime::spawn(async move {
334		dev_log!("lifecycle", "[Lifecycle] [PostSetup] Starting...");
335		let PostSetupStart = crate::IPC::DevLog::NowNano();
336		let AppStateForSetup = PostSetupEnvironment.ApplicationState.clone();
337		TraceStep!("[Lifecycle] [PostSetup] AppState cloned.");
338
339		// [Config]
340		let ConfigStart = crate::IPC::DevLog::NowNano();
341		let _ = ConfigurationInitializeFn(&PostSetupEnvironment).await;
342		crate::otel_span!("lifecycle:config:initialize", ConfigStart);
343
344		// [Workspace] [Trust] Desktop app — trust local workspace by default
345		AppStateForSetup.Workspace.SetTrustStatus(true);
346
347		// [Extensions] [ScanPaths]
348		let ExtScanStart = crate::IPC::DevLog::NowNano();
349		let _ = ScanPathConfigureFn(&AppStateForSetup);
350
351		// [Extensions] [Scan]
352		let _ = ExtensionPopulateFn(PostSetupAppHandle.clone(), &AppStateForSetup).await;
353		crate::otel_span!("lifecycle:extensions:scan", ExtScanStart);
354
355		// [Vine] [gRPC]
356		let VineStart = crate::IPC::DevLog::NowNano();
357		let _ = VineStartFn(
358			PostSetupAppHandle.clone(),
359			"127.0.0.1:50051".to_string(),
360			"127.0.0.1:50052".to_string(),
361		)
362		.await;
363		crate::otel_span!("lifecycle:vine:start", VineStart);
364
365		// [Cocoon] [Sidecar]
366		let CocoonStart = crate::IPC::DevLog::NowNano();
367		let _ = CocoonStartFn(&PostSetupAppHandle, &PostSetupEnvironment).await;
368		crate::otel_span!("lifecycle:cocoon:start", CocoonStart);
369
370		crate::otel_span!("lifecycle:postsetup:complete", PostSetupStart);
371		dev_log!("lifecycle", "[Lifecycle] [PostSetup] Complete. System ready.");
372	});
373
374	Ok(())
375}