1use std::sync::{
60 Arc,
61 atomic::{AtomicBool, Ordering},
62};
63
64use tauri::{App, Manager, RunEvent, Wry};
65use Echo::Scheduler::{Scheduler::Scheduler, SchedulerBuilder::SchedulerBuilder};
66
67use crate::dev_log;
68use crate::{
69 ApplicationState::State::ApplicationState::ApplicationState,
71 Binary::Build::DnsCommands::{
72 StartupTime::init_dns_startup_time,
73 dns_get_forward_allowlist::dns_get_forward_allowlist,
74 dns_get_health_status::dns_get_health_status,
75 dns_get_server_info::dns_get_server_info,
76 dns_get_zone_info::dns_get_zone_info,
77 dns_health_check::dns_health_check,
78 dns_resolve::dns_resolve,
79 dns_test_resolution::dns_test_resolution,
80 },
81 Binary::Build::LocalhostPlugin::LocalhostPlugin as LocalhostPluginFn,
83 Binary::Build::LoggingPlugin::LoggingPlugin as LoggingPluginFn,
84 Binary::Build::Scheme::{self, DnsPort, init_service_registry, land_scheme_handler, register_land_service},
85 Binary::Build::ServiceRegistry::ServiceRegistry as ServiceRegistryFn,
86 Binary::Build::TauriBuild::TauriBuild as TauriBuildFn,
87 Binary::Build::WindowBuild::WindowBuild as WindowBuildFn,
88 Binary::Extension::ExtensionPopulate::Fn as ExtensionPopulateFn,
89 Binary::Extension::ScanPathConfigure::ScanPathConfigure as ScanPathConfigureFn,
90 Binary::Initialize::CliParse::Parse as CliParseFn,
91 Binary::Initialize::LogLevel::Resolve as ResolveLogLevel,
92 Binary::Initialize::PortSelector::BuildUrl as BuildPortUrl,
93 Binary::Initialize::PortSelector::Select as SelectPort,
94 Binary::Initialize::StateBuild::Build as BuildStateFn,
95 Binary::Register::AdvancedFeaturesRegister::AdvancedFeaturesRegister as AdvancedFeaturesRegisterFn,
96 Binary::Register::CommandRegister::CommandRegister as CommandRegisterFn,
97 Binary::Register::IPCServerRegister::IPCServerRegister as IPCServerRegisterFn,
98 Binary::Register::StatusReporterRegister::StatusReporterRegister as StatusReporterRegisterFn,
99 Binary::Register::WindSyncRegister::WindSyncRegister as WindSyncRegisterFn,
100 Binary::Service::CocoonStart::Fn as CocoonStartFn,
101 Binary::Service::ConfigurationInitialize::Fn as ConfigurationInitializeFn,
102 Binary::Service::VineStart::Fn as VineStartFn,
103 Binary::Shutdown::RuntimeShutdown::RuntimeShutdown as RuntimeShutdownFn,
104 Binary::Shutdown::SchedulerShutdown::SchedulerShutdown as SchedulerShutdownFn,
105 Command,
106 Environment::MountainEnvironment::MountainEnvironment,
107 ProcessManagement::InitializationData,
108 RunTime::ApplicationRunTime::ApplicationRunTime,
109 Track,
110};
111use super::AppLifecycle::AppLifecycleSetup;
112
113macro_rules! TraceStep {
118
119 ($($arg:tt)*) => {{
120
121 dev_log!("lifecycle", $($arg)*);
122 }};
123}
124
125pub fn Fn() {
139 match keyring::use_native_store(false) {
150 Ok(()) => dev_log!("lifecycle", "[Boot] [Keyring] Native store initialized for secret management"),
151
152 Err(E) => {
153 dev_log!(
154 "lifecycle",
155 "warn: [Boot] [Keyring] Failed to initialize native store ({}); secret operations will fall back to \
156 no-op",
157 E
158 )
159 },
160 }
161
162 crate::IPC::DevLog::InitEager::Fn();
171
172 let IsTtyLaunch =
189 std::env::var("TERM_PROGRAM").is_ok() || std::env::var("TERM").map_or(false, |V| V != "dumb" && V != "unknown");
190
191 if !IsTtyLaunch {
192 crate::Environment::Utility::EnhanceShellEnvironment::Fn();
193 }
194
195 if !IsTtyLaunch {
202 {
203 fn LoadEnvFile(Path:&std::path::Path) -> bool {
204 let Ok(Content) = std::fs::read_to_string(Path) else {
205 return false;
206 };
207
208 for Line in Content.lines() {
209 let Trimmed = Line.trim();
210
211 if Trimmed.is_empty() || Trimmed.starts_with('#') {
212 continue;
213 }
214
215 if let Some((Key, Value)) = Trimmed.split_once('=') {
216 let CleanKey = Key.trim();
217
218 let CleanValue = Value.trim().trim_matches('"').trim_matches('\'');
219
220 if std::env::var_os(CleanKey).is_none() {
221 unsafe { std::env::set_var(CleanKey, CleanValue) };
225 }
226 }
227 }
228
229 true
230 }
231
232 let mut Candidates:Vec<std::path::PathBuf> = Vec::new();
233
234 if let Ok(Cwd) = std::env::current_dir() {
235 Candidates.push(Cwd.join(".env.Land"));
236
237 if let Some(Parent) = Cwd.parent() {
238 Candidates.push(Parent.join(".env.Land"));
239 }
240
241 Candidates.push(Cwd.join(".env.Land.Sample"));
242
243 if let Some(Parent) = Cwd.parent() {
244 Candidates.push(Parent.join(".env.Land.Sample"));
245 }
246 }
247
248 if let Ok(Exe) = std::env::current_exe() {
250 let Ancestors:Vec<&std::path::Path> = Exe.ancestors().collect();
251
252 for Candidate in Ancestors.iter().take(6) {
253 Candidates.push(Candidate.join(".env.Land"));
254
255 Candidates.push(Candidate.join(".env.Land.Sample"));
256 }
257 }
258
259 let mut Loaded = false;
260
261 for Candidate in Candidates {
262 if Candidate.exists() && LoadEnvFile(&Candidate) {
263 crate::dev_log!("lifecycle", "[Boot] [Env] Loaded env from {}", Candidate.display());
264
265 Loaded = true;
266
267 break;
268 }
269 }
270
271 if !Loaded {
272 crate::dev_log!(
273 "lifecycle",
274 "[Boot] [Env] No .env.Land / .env.Land.Sample found - using defaults"
275 );
276 }
277 }
278 }
279
280 crate::LandFixTier::LogResolvedTiers();
284
285 {
296 let NamedProfile = option_env!("Profile").unwrap_or("unknown");
297
298 let Workbench = option_env!("Pack").unwrap_or("Unknown");
299
300 let Bundle = option_env!("Bundle").unwrap_or("");
301
302 let Compiler = option_env!("Compiler").unwrap_or("default");
303
304 dev_log!(
305 "lifecycle",
306 "[LandFix:Profile] Active profile={} workbench={} bundle={} compiler={}",
307 NamedProfile,
308 Workbench,
309 Bundle,
310 Compiler
311 );
312 }
313
314 TraceStep!("[Boot] [Runtime] Building Tokio runtime...");
318
319 let Runtime = tokio::runtime::Builder::new_multi_thread()
320 .enable_all()
321 .build()
322 .expect("FATAL: Cannot build Tokio runtime.");
323
324 TraceStep!("[Boot] [Runtime] Tokio runtime built.");
325
326 Runtime.block_on(async {
327 crate::Binary::Build::PostHogPlugin::HydrateRuntimeEnvironment::Fn();
337
338 crate::Binary::Build::PostHogPlugin::Initialize::Fn().await;
344
345 CommonLibrary::Telemetry::Initialize::Fn(CommonLibrary::Telemetry::Tier::Tier::Mountain).await;
354
355 let _WorkspaceConfigurationPath = CliParseFn();
359 let _InitialFolders:Vec<String> = vec![];
360
361 dev_log!("lifecycle", "[Boot] [State] Building ApplicationState...");
365
366 let AppState = ApplicationState::default();
368
369 {
377 let InitialFolderPaths = crate::Binary::Initialize::CliParse::ParseWorkspaceFolders();
378 if InitialFolderPaths.is_empty() {
379 dev_log!(
380 "lifecycle",
381 "[Boot] [Workspace] No initial folders resolved - editor will open in \"no folder\" mode."
382 );
383 } else {
384 use crate::ApplicationState::DTO::WorkspaceFolderStateDTO::WorkspaceFolderStateDTO;
385 let mut Folders:Vec<WorkspaceFolderStateDTO> = Vec::new();
386 for (Index, Path) in InitialFolderPaths.iter().enumerate() {
387 let Uri = match url::Url::from_directory_path(Path) {
388 Ok(U) => U,
389 Err(()) => {
390 dev_log!(
391 "lifecycle",
392 "warn: [Boot] [Workspace] Failed to build URL for {}; skipping",
393 Path.display()
394 );
395 continue;
396 },
397 };
398 let Name = Path
399 .file_name()
400 .and_then(|N| N.to_str())
401 .map(str::to_string)
402 .unwrap_or_else(|| Path.display().to_string());
403 match WorkspaceFolderStateDTO::New(Uri, Name, Index) {
404 Ok(Dto) => Folders.push(Dto),
405 Err(Error) => {
406 dev_log!(
407 "lifecycle",
408 "warn: [Boot] [Workspace] Failed to build folder DTO for {}: {}",
409 Path.display(),
410 Error
411 );
412 },
413 }
414 }
415 if !Folders.is_empty() {
416 AppState.Workspace.SetWorkspaceFolders(Folders);
421 dev_log!(
422 "lifecycle",
423 "[Boot] [Workspace] Seeded {} workspace folder(s).",
424 InitialFolderPaths.len()
425 );
426 }
427 }
428 }
429
430 dev_log!(
431 "lifecycle",
432 "[Boot] [State] ApplicationState created with {} workspace folders.",
433 AppState.Workspace.WorkspaceFolders.lock().map(|f| f.len()).unwrap_or(0)
434 );
435
436 let AppStateArcForClosure = Arc::new(AppState.clone());
438
439 let Scheduler = Arc::new(SchedulerBuilder::Create().Build());
443 let SchedulerForClosure = Scheduler.clone();
444 TraceStep!("[Boot] [Echo] Scheduler handles prepared.");
445
446 let ServerPort = SelectPort();
450 let LocalhostUrl = BuildPortUrl(ServerPort);
451
452 let log_level = ResolveLogLevel();
456
457 let Builder = TauriBuildFn();
461
462 Builder
463 .plugin(LoggingPluginFn(log_level))
464 .plugin(LocalhostPluginFn(ServerPort))
465 .manage(AppStateArcForClosure.clone())
466 .setup({
467 let LocalhostUrl = LocalhostUrl.clone();
468 let ServerPortForClosure = ServerPort;
469 move |app:&mut App| {
470 dev_log!("lifecycle", "[Lifecycle] [Setup] Setup hook started.");
471 dev_log!("lifecycle", "[Lifecycle] [Setup] LocalhostUrl={}", LocalhostUrl);
472
473 dev_log!(
477 "lifecycle",
478 "[Lifecycle] [Setup] Initializing ServiceRegistry for land:// scheme..."
479 );
480 let service_registry = ServiceRegistryFn::new();
481 init_service_registry(service_registry.clone());
482
483 dev_log!(
488 "lifecycle",
489 "[Lifecycle] [Setup] Registering code.land.playform.cloud service on port {}",
490 ServerPortForClosure
491 );
492 register_land_service("code.land.playform.cloud", ServerPortForClosure);
493
494 register_land_service("api.land.playform.cloud", ServerPortForClosure);
496
497 register_land_service("assets.land.playform.cloud", ServerPortForClosure);
499
500 app.manage(service_registry);
502 dev_log!(
503 "lifecycle",
504 "[Lifecycle] [Setup] ServiceRegistry initialized and services registered."
505 );
506
507 dev_log!("lifecycle", "[Lifecycle] [Setup] Starting DNS server on preferred port 5380...");
513 let dns_port = Mist::start(5380).unwrap_or_else(|e| {
514 dev_log!(
515 "lifecycle",
516 "warn: [Lifecycle] [Setup] Failed to start DNS server on port 5380: {}",
517 e
518 );
519 Mist::start(0).unwrap_or_else(|e| {
521 dev_log!(
522 "lifecycle",
523 "error: [Lifecycle] [Setup] Completely failed to start DNS server: {}",
524 e
525 );
526 0 })
528 });
529
530 if dns_port == 0 {
531 dev_log!(
532 "lifecycle",
533 "warn: [Lifecycle] [Setup] DNS server failed to start, land:// protocol will not be \
534 available"
535 );
536 } else {
537 dev_log!(
538 "lifecycle",
539 "[Lifecycle] [Setup] DNS server started successfully on port {}",
540 dns_port
541 );
542 crate::Binary::Build::DnsCommands::StartupTime::init_dns_startup_time();
544 }
545
546 let TierWebSocketSetting = std::env::var("TierWebSocket")
561 .unwrap_or_else(|_| env!("TierWebSocket", "Disabled").to_string());
562
563 if TierWebSocketSetting == "Mist" {
564 dev_log!(
565 "lifecycle",
566 "[Lifecycle] [Setup] TierWebSocket=Mist - starting WebSocket transport on 127.0.0.1:5051"
567 );
568
569 let MistRegistry = Mist::WebSocket::HandlerRegistry::new();
570 let MistSecret = Mist::WebSocket::SharedSecret::random();
571
572 unsafe {
577 std::env::set_var("MountainWebSocketSecret", MistSecret.as_hex());
578 std::env::set_var("MountainWebSocketPort", "5051");
579 }
580
581 tokio::spawn(async move {
582 if let Err(Error) = Mist::WebSocket::ServeLocal(5051, MistSecret, MistRegistry).await {
583 dev_log!("lifecycle", "warn: [Lifecycle] [Mist] WebSocket server exited: {:?}", Error);
584 }
585 });
586 } else {
587 dev_log!(
588 "lifecycle",
589 "[Lifecycle] [Setup] TierWebSocket={} - WebSocket transport disabled",
590 TierWebSocketSetting
591 );
592 }
593
594 app.manage(DnsPort(dns_port));
596
597 let AppHandle = app.handle().clone();
598 TraceStep!("[Lifecycle] [Setup] AppHandle acquired.");
599
600 let AppStateArcFromClosure = AppStateArcForClosure.clone();
604
605 if let Err(e) = AppLifecycleSetup(
606 app,
607 AppHandle.clone(),
608 LocalhostUrl.clone(),
609 SchedulerForClosure.clone(),
610 AppStateArcFromClosure,
611 ) {
612 dev_log!("lifecycle", "error: [Lifecycle] [Setup] Failed to setup lifecycle: {}", e);
613 }
614
615 Ok(())
616 }
617 })
618 .register_asynchronous_uri_scheme_protocol("fiddee", |_ctx, request, responder| {
619 let response = crate::Binary::Build::Scheme::land_scheme_handler(&request);
621 responder.respond(response);
622 })
623 .register_asynchronous_uri_scheme_protocol("vscode-file", |ctx, request, responder| {
624 let AppHandle = ctx.app_handle().clone();
627 std::thread::spawn(move || {
628 let response = crate::Binary::Build::Scheme::VscodeFileSchemeHandler(&AppHandle, &request);
629 responder.respond(response);
630 });
631 })
632 .register_asynchronous_uri_scheme_protocol("vscode-webview", |ctx, request, responder| {
633 let AppHandle = ctx.app_handle().clone();
643 std::thread::spawn(move || {
644 let response = crate::Binary::Build::Scheme::VscodeWebviewSchemeHandler(&AppHandle, &request);
645 responder.respond(response);
646 });
647 })
648 .register_asynchronous_uri_scheme_protocol("vscode-webview-resource", |ctx, request, responder| {
649 let AppHandle = ctx.app_handle().clone();
662 std::thread::spawn(move || {
663 let Original = request.uri().to_string();
664 let RewrittenUri = match Original.strip_prefix("vscode-webview-resource://") {
665 Some(After) => {
666 let Rest = After.find('/').map(|I| &After[I..]).unwrap_or("/");
667 format!("vscode-file://vscode-app{}", Rest)
668 },
669 None => "vscode-file://vscode-app/".to_string(),
670 };
671 crate::dev_log!(
672 "scheme-assets",
673 "[LandFix:VscodeWebviewResource] {} -> {}",
674 Original,
675 RewrittenUri
676 );
677 let mut Builder = tauri::http::request::Request::builder().uri(&RewrittenUri);
678 for (Name, Value) in request.headers().iter() {
679 Builder = Builder.header(Name, Value);
680 }
681 let Forwarded = Builder
682 .method(request.method().clone())
683 .body(request.body().clone())
684 .unwrap_or_else(|_| request.clone());
685 let response = crate::Binary::Build::Scheme::VscodeFileSchemeHandler(&AppHandle, &Forwarded);
686 responder.respond(response);
687 });
688 })
689 .register_asynchronous_uri_scheme_protocol("vscode-resource", |ctx, request, responder| {
690 let AppHandle = ctx.app_handle().clone();
694 std::thread::spawn(move || {
695 let Original = request.uri().to_string();
696 let RewrittenUri = match Original.strip_prefix("vscode-resource://") {
697 Some(After) => {
698 let Rest = After.find('/').map(|I| &After[I..]).unwrap_or("/");
699 format!("vscode-file://vscode-app{}", Rest)
700 },
701 None => "vscode-file://vscode-app/".to_string(),
702 };
703 crate::dev_log!("scheme-assets", "[LandFix:VscodeResource] {} -> {}", Original, RewrittenUri);
704 let mut Builder = tauri::http::request::Request::builder().uri(&RewrittenUri);
705 for (Name, Value) in request.headers().iter() {
706 Builder = Builder.header(Name, Value);
707 }
708 let Forwarded = Builder
709 .method(request.method().clone())
710 .body(request.body().clone())
711 .unwrap_or_else(|_| request.clone());
712 let response = crate::Binary::Build::Scheme::VscodeFileSchemeHandler(&AppHandle, &Forwarded);
713 responder.respond(response);
714 });
715 })
716 .plugin(tauri_plugin_dialog::init())
717 .plugin(tauri_plugin_fs::init())
718 .invoke_handler(tauri::generate_handler![
719 crate::Binary::Tray::SwitchTrayIcon::SwitchTrayIcon,
720
721 crate::Binary::IPC::WorkbenchConfigurationCommand::MountainGetWorkbenchConfiguration,
722
723 Command::TreeView::GetTreeViewChildren::GetTreeViewChildren,
724
725 Command::LanguageFeature::MountainProvideHover::MountainProvideHover,
726
727 Command::LanguageFeature::MountainProvideCompletions::MountainProvideCompletions,
728
729 Command::LanguageFeature::MountainProvideDefinition::MountainProvideDefinition,
730
731 Command::LanguageFeature::MountainProvideReferences::MountainProvideReferences,
732
733 Command::SourceControlManagement::GetAllSourceControlManagementState::GetAllSourceControlManagementState,
734
735 Command::Keybinding::GetResolvedKeybinding::GetResolvedKeybinding,
736
737 Track::FrontendCommand::DispatchFrontendCommand::DispatchFrontendCommand,
738
739 Track::UIRequest::ResolveUIRequest::ResolveUIRequest,
740
741 Track::Webview::MountainWebviewPostMessageFromGuest::MountainWebviewPostMessageFromGuest,
742
743 crate::Binary::IPC::MessageReceiveCommand::MountainIPCReceiveMessage,
744
745 crate::Binary::IPC::StatusGetCommand::MountainIPCGetStatus,
746
747 crate::Binary::IPC::InvokeCommand::MountainIPCInvoke,
748
749 crate::Binary::IPC::WindConfigurationCommand::MountainGetWindDesktopConfiguration,
750
751 crate::Binary::IPC::ConfigurationUpdateCommand::MountainUpdateConfigurationFromWind,
752
753 crate::Binary::IPC::ConfigurationSyncCommand::MountainSynchronizeConfiguration,
754
755 crate::Binary::IPC::ConfigurationStatusCommand::MountainGetConfigurationStatus,
756
757 crate::Binary::IPC::IPCStatusCommand::MountainGetIPCStatus,
758
759 crate::Binary::IPC::IPCStatusHistoryCommand::MountainGetIPCStatusHistory,
760
761 crate::Binary::IPC::IPCStatusReportingStartCommand::MountainStartIPCStatusReporting,
762
763 crate::Binary::IPC::PerformanceStatsCommand::MountainGetPerformanceStats,
764
765 crate::Binary::IPC::CacheStatsCommand::MountainGetCacheStats,
766
767 crate::Binary::IPC::CollaborationSessionCommand::MountainCreateCollaborationSession,
768
769 crate::Binary::IPC::CollaborationSessionCommand::MountainGetCollaborationSessions,
770
771 crate::Binary::IPC::DocumentSyncCommand::MountainAddDocumentForSync,
772
773 crate::Binary::IPC::DocumentSyncCommand::MountainGetSyncStatus,
774
775 crate::Binary::IPC::UpdateSubscriptionCommand::MountainSubscribeToUpdates,
776
777 crate::Binary::IPC::ConfigurationDataCommand::GetConfigurationData,
778
779 crate::Binary::IPC::ConfigurationDataCommand::SaveConfigurationData,
780
781 crate::Binary::IPC::WorkspaceFolderCommand::MountainWorkspaceOpenFolder,
782
783 crate::Binary::IPC::WorkspaceFolderCommand::MountainWorkspaceListFolders,
784
785 crate::Binary::IPC::WorkspaceFolderCommand::MountainWorkspaceCloseAllFolders,
786
787 crate::Binary::Build::DnsCommands::dns_get_server_info::dns_get_server_info,
788
789 crate::Binary::Build::DnsCommands::dns_get_zone_info::dns_get_zone_info,
790
791 crate::Binary::Build::DnsCommands::dns_get_forward_allowlist::dns_get_forward_allowlist,
792
793 crate::Binary::Build::DnsCommands::dns_get_health_status::dns_get_health_status,
794
795 crate::Binary::Build::DnsCommands::dns_resolve::dns_resolve,
796
797 crate::Binary::Build::DnsCommands::dns_test_resolution::dns_test_resolution,
798
799 crate::Binary::Build::DnsCommands::dns_health_check::dns_health_check,
800
801 crate::Binary::IPC::ProcessCommand::process_get_exec_path::process_get_exec_path,
803
804 crate::Binary::IPC::ProcessCommand::process_get_platform::process_get_platform,
805
806 crate::Binary::IPC::ProcessCommand::process_get_arch::process_get_arch,
807
808 crate::Binary::IPC::ProcessCommand::process_get_pid::process_get_pid,
809
810 crate::Binary::IPC::ProcessCommand::process_get_shell_env::process_get_shell_env,
811
812 crate::Binary::IPC::ProcessCommand::process_get_memory_info::process_get_memory_info,
813
814 crate::Binary::IPC::HealthCommand::cocoon_extension_host_health::cocoon_extension_host_health,
816
817 crate::Binary::IPC::HealthCommand::cocoon_search_service_health::cocoon_search_service_health,
818
819 crate::Binary::IPC::HealthCommand::cocoon_debug_service_health::cocoon_debug_service_health,
820
821 crate::Binary::IPC::HealthCommand::shared_process_service_health::shared_process_service_health,
822
823 crate::Binary::IPC::RenderDevLogCommand::RenderDevLog,
824
825 crate::Binary::IPC::VineSubscribeCommand::vine_subscribe_notifications,
834
835 crate::Binary::IPC::VineSubscribeCommand::vine_subscriber_count,
836 ])
837 .build(tauri::generate_context!())
838 .expect("FATAL: Error while building Mountain Tauri application")
839 .run(move |app_handle:&tauri::AppHandle, event:tauri::RunEvent| {
840 if cfg!(debug_assertions) {
842 match &event {
843 RunEvent::MainEventsCleared => {},
844 RunEvent::WindowEvent { .. } => {},
845 _ => dev_log!("lifecycle", "[Lifecycle] [RunEvent] {:?}", event),
846 }
847 }
848
849 if let RunEvent::ExitRequested { api, .. } = event {
850 static SHUTTING_DOWN:AtomicBool = AtomicBool::new(false);
858 if SHUTTING_DOWN.swap(true, Ordering::SeqCst) {
859 return;
860 }
861
862 dev_log!(
863 "lifecycle",
864 "warn: [Lifecycle] [Shutdown] Exit requested. Starting graceful shutdown..."
865 );
866 api.prevent_exit();
867
868 let SchedulerHandle = Scheduler.clone();
869 let app_handle_clone = app_handle.clone();
870
871 tokio::spawn(async move {
872 dev_log!("lifecycle", "[Lifecycle] [Shutdown] Shutting down ApplicationRunTime...");
873 let _ = RuntimeShutdownFn(&app_handle_clone).await;
874
875 dev_log!("lifecycle", "[Lifecycle] [Shutdown] Stopping Echo scheduler...");
876 let _ = SchedulerShutdownFn(SchedulerHandle).await;
877
878 dev_log!("lifecycle", "[Lifecycle] [Shutdown] Done. Exiting process.");
879 app_handle_clone.exit(0);
880 });
881 }
882 });
883
884 dev_log!("lifecycle", "[Lifecycle] [Exit] Mountain application has shut down.");
885 });
886}