Skip to main content

Mountain/Binary/Main/
Entry.rs

1#![allow(unused_imports)]
2
3//! # Entry (Binary/Main)
4//!
5//! ## RESPONSIBILITIES
6//!
7//! Main application entry point that orchestrates the complete application
8//! lifecycle. This function coordinates:
9//! - Tokio runtime creation and management
10//! - CLI argument parsing
11//! - Application state initialization
12//! - Tauri application builder setup
13//! - Service initialization (Vine, Cocoon, Configuration)
14//! - Graceful shutdown handling
15//!
16//! ## ARCHITECTURAL ROLE
17//!
18//! The Entry module is the **primary entry point** in Mountain's architecture:
19//!
20//! ```text
21//! main.rs ──► Binary::Main::Entry::Fn()
22//!                                    │
23//!                                    ▼
24//! AppLifecycle ──► Service Initialization ──► Tauri App Run
25//!                                           │
26//!                                           ▼
27//!                                   Graceful Shutdown
28//! ```
29//!
30//! ## KEY COMPONENTS
31//!
32//! - **Fn()**: Main entry point exported as `Binary::Main::Fn()`
33//! - Tokio runtime management
34//! - Application state initialization via StateBuild
35//! - Tauri builder configuration via TauriBuild
36//! - Service orchestration (Vine, Cocoon, Configuration)
37//! - Event-driven lifecycle management
38//!
39//! ## ERROR HANDLING
40//!
41//! - Panics on fatal errors (Tokio runtime failure, Tauri build failure)
42//! - Logs errors for service initialization failures
43//! - Graceful degradation for non-critical service failures
44//!
45//! ## LOGGING
46//!
47//! Uses the TraceStep! macro for checkpoint logging at TRACE level.
48//! Additional logging at DEBUG, INFO, WARN, and ERROR levels throughout.
49//!
50//! ## PERFORMANCE CONSIDERATIONS
51//!
52//! - Tokio multi-threaded runtime for optimal performance
53//! - Asynchronous service initialization
54//! - Lazy initialization where possible
55//!
56//! ## TODO
57//! - [ ] Add crash recovery mechanism
58//! - [ ] Implement proper error dialog for startup failures
59//! - [ ] Add startup performance metrics
60
61use std::sync::{
62	Arc,
63	atomic::{AtomicBool, Ordering},
64};
65
66use tauri::{App, Manager, RunEvent, Wry};
67use Echo::Scheduler::{Scheduler::Scheduler, SchedulerBuilder::SchedulerBuilder};
68
69use crate::dev_log;
70use crate::{
71	// Crate root imports
72	ApplicationState::State::ApplicationState::ApplicationState,
73	Binary::Build::DnsCommands::{
74		StartupTime::init_dns_startup_time,
75		dns_get_forward_allowlist::dns_get_forward_allowlist,
76		dns_get_health_status::dns_get_health_status,
77		dns_get_server_info::dns_get_server_info,
78		dns_get_zone_info::dns_get_zone_info,
79		dns_health_check::dns_health_check,
80		dns_resolve::dns_resolve,
81		dns_test_resolution::dns_test_resolution,
82	},
83	// Binary submodule imports
84	Binary::Build::LocalhostPlugin::LocalhostPlugin as LocalhostPluginFn,
85	Binary::Build::LoggingPlugin::LoggingPlugin as LoggingPluginFn,
86	Binary::Build::Scheme::{self, DnsPort, init_service_registry, land_scheme_handler, register_land_service},
87	Binary::Build::ServiceRegistry::ServiceRegistry as ServiceRegistryFn,
88	Binary::Build::TauriBuild::TauriBuild as TauriBuildFn,
89	Binary::Build::WindowBuild::WindowBuild as WindowBuildFn,
90	Binary::Extension::ExtensionPopulate::Fn as ExtensionPopulateFn,
91	Binary::Extension::ScanPathConfigure::ScanPathConfigure as ScanPathConfigureFn,
92	Binary::Initialize::CliParse::Parse as CliParseFn,
93	Binary::Initialize::LogLevel::Resolve as ResolveLogLevel,
94	Binary::Initialize::PortSelector::BuildUrl as BuildPortUrl,
95	Binary::Initialize::PortSelector::Select as SelectPort,
96	Binary::Initialize::StateBuild::Build as BuildStateFn,
97	Binary::Register::AdvancedFeaturesRegister::AdvancedFeaturesRegister as AdvancedFeaturesRegisterFn,
98	Binary::Register::CommandRegister::CommandRegister as CommandRegisterFn,
99	Binary::Register::IPCServerRegister::IPCServerRegister as IPCServerRegisterFn,
100	Binary::Register::StatusReporterRegister::StatusReporterRegister as StatusReporterRegisterFn,
101	Binary::Register::WindSyncRegister::WindSyncRegister as WindSyncRegisterFn,
102	Binary::Service::CocoonStart::Fn as CocoonStartFn,
103	Binary::Service::ConfigurationInitialize::Fn as ConfigurationInitializeFn,
104	Binary::Service::VineStart::Fn as VineStartFn,
105	Binary::Shutdown::RuntimeShutdown::RuntimeShutdown as RuntimeShutdownFn,
106	Binary::Shutdown::SchedulerShutdown::SchedulerShutdown as SchedulerShutdownFn,
107	Command,
108	Environment::MountainEnvironment::MountainEnvironment,
109	ProcessManagement::InitializationData,
110	RunTime::ApplicationRunTime::ApplicationRunTime,
111	Track,
112};
113use super::AppLifecycle::AppLifecycleSetup;
114
115// Note: Tauri commands are used with fully qualified paths in generate_handler
116// because the __cmd_* macros generated by #[tauri::command] are module-local.
117
118/// Logs a checkpoint message at TRACE level.
119macro_rules! TraceStep {
120
121	($($arg:tt)*) => {{
122
123		dev_log!("lifecycle", $($arg)*);
124	}};
125}
126
127/// The main function that orchestrates the application lifecycle.
128///
129/// This function:
130/// 1. Creates a Tokio runtime
131/// 2. Parses CLI arguments
132/// 3. Builds application state
133/// 4. Creates a scheduler
134/// 5. Selects a port for the local server
135/// 6. Resolves the log level
136/// 7. Sets up the Tauri builder
137/// 8. Configures the application lifecycle
138/// 9. Runs the Tauri application
139/// 10. Handles graceful shutdown
140pub fn Fn() {
141	// Initialize the native keyring store (Keychain on macOS) before any
142	// code path that calls SecretProvider. keyring-core 1.0 requires an
143	// explicit store set via set_default_store() before Entry::new() can
144	// create or look up credentials. omitting this causes "No default
145	// store has been set, so cannot search or create entries" on every
146	// secrets.get call, which in turn prevents extensions (e.g. Roo Code)
147	// from completing their webview initialisation.
148	//
149	// The `not_keyutils` parameter only matters on Linux - macOS ignores
150	// it and always routes to the native Keychain.
151	match keyring::use_native_store(false) {
152		Ok(()) => dev_log!("lifecycle", "[Boot] [Keyring] Native store initialized for secret management"),
153
154		Err(E) => {
155			dev_log!(
156				"lifecycle",
157				"warn: [Boot] [Keyring] Failed to initialize native store ({}); secret operations will fall back to \
158				 no-op",
159				E
160			)
161		},
162	}
163
164	// Open `Mountain.dev.log` up front. Forces `InitFileSink` to create
165	// the session log header on disk before any other code can panic, so
166	// an early crash still leaves a file with a timestamp + pid + tag
167	// context for post-mortem. Env vars are read from the shell here (the
168	// `.env.Land` load below may add MORE keys but never overrides
169	// Trace / Record because `set_var` only runs when a
170	// key is currently unset). Harmless to call: the inner `OnceLock`
171	// gates repeat invocations.
172	crate::IPC::DevLog::InitEager::Fn();
173
174	// -------------------------------------------------------------------------
175	// [Boot] [Env] Enhance the process environment with the user's
176	// interactive-shell PATH / NVM_DIR / HOMEBREW_PREFIX / JAVA_HOME / …
177	// before any child process is spawned. macOS GUI launches (Finder,
178	// Dock, Spotlight, `open <bundle>.app`) start the app with a minimal
179	// env where Homebrew, NVM, and similar are not on PATH; without this
180	// step the Cocoon node binary, language servers, and `git` calls
181	// from extensions all fall back to system defaults (or fail).
182	//
183	// Skip entirely when launched from a TTY (terminal already has the
184	// right env). On macOS, `std::io::stdin().is_terminal()` is the
185	// canonical check - waits for is-terminal 0.5; in the interim,
186	// probe `TERM_PROGRAM` env var which macOS Terminal.app and iTerm2
187	// both set. `TERM=xterm-256color` alone is unreliable (pipelines
188	// set it too). No-op when skip or the shell probe fails/times out.
189	// -------------------------------------------------------------------------
190	let IsTtyLaunch =
191		std::env::var("TERM_PROGRAM").is_ok() || std::env::var("TERM").map_or(false, |V| V != "dumb" && V != "unknown");
192
193	if !IsTtyLaunch {
194		crate::Environment::Utility::EnhanceShellEnvironment::Fn();
195	}
196
197	// -------------------------------------------------------------------------
198	// [Boot] [Env] Load .env.Land into process env so standalone binary
199	// invocations pick up Product*, Tier*, Network* vars without requiring
200	// the shell to pre-source the env file. Skip when launched from a TTY
201	// (terminal already has the right env).
202	// -------------------------------------------------------------------------
203	if !IsTtyLaunch {
204		{
205			fn LoadEnvFile(Path:&std::path::Path) -> bool {
206				let Ok(Content) = std::fs::read_to_string(Path) else {
207					return false;
208				};
209
210				for Line in Content.lines() {
211					let Trimmed = Line.trim();
212
213					if Trimmed.is_empty() || Trimmed.starts_with('#') {
214						continue;
215					}
216
217					if let Some((Key, Value)) = Trimmed.split_once('=') {
218						let CleanKey = Key.trim();
219
220						let CleanValue = Value.trim().trim_matches('"').trim_matches('\'');
221
222						if std::env::var_os(CleanKey).is_none() {
223							// SAFETY: set_var is called once per key during bootstrap
224							// before any threads read env (Tokio runtime starts later
225							// in this function).
226							unsafe { std::env::set_var(CleanKey, CleanValue) };
227						}
228					}
229				}
230
231				true
232			}
233
234			let mut Candidates:Vec<std::path::PathBuf> = Vec::new();
235
236			if let Ok(Cwd) = std::env::current_dir() {
237				Candidates.push(Cwd.join(".env.Land"));
238
239				if let Some(Parent) = Cwd.parent() {
240					Candidates.push(Parent.join(".env.Land"));
241				}
242
243				Candidates.push(Cwd.join(".env.Land.Sample"));
244
245				if let Some(Parent) = Cwd.parent() {
246					Candidates.push(Parent.join(".env.Land.Sample"));
247				}
248			}
249
250			// Repo-layout probe: Target/debug/<bin> → four hops up lands at Land/.
251			if let Ok(Exe) = std::env::current_exe() {
252				let Ancestors:Vec<&std::path::Path> = Exe.ancestors().collect();
253
254				for Candidate in Ancestors.iter().take(6) {
255					Candidates.push(Candidate.join(".env.Land"));
256
257					Candidates.push(Candidate.join(".env.Land.Sample"));
258				}
259			}
260
261			let mut Loaded = false;
262
263			for Candidate in Candidates {
264				if Candidate.exists() && LoadEnvFile(&Candidate) {
265					crate::dev_log!("lifecycle", "[Boot] [Env] Loaded env from {}", Candidate.display());
266
267					Loaded = true;
268
269					break;
270				}
271			}
272
273			if !Loaded {
274				crate::dev_log!(
275					"lifecycle",
276					"[Boot] [Env] No .env.Land / .env.Land.Sample found - using defaults"
277				);
278			}
279		}
280	}
281
282	// -------------------------------------------------------------------------
283	// [Boot] [Tier] Resolved tier banner (Plan A Wave 1.7 runtime banner)
284	// -------------------------------------------------------------------------
285	crate::LandFixTier::LogResolvedTiers();
286
287	// -------------------------------------------------------------------------
288	// [Boot] [Profile] Self-report (BATCH-13 step 6)
289	//
290	// Build.sh exports `Browser`/`Mountain`/`Electron`/`Bundle`/`Compiler`/
291	// `Profile` into the shell that invokes cargo. `build.rs` captures
292	// those into `cargo:rustc-env=LAND_*` so they're baked into the binary -
293	// runtime env lookups don't survive launching the binary from Finder /
294	// another shell. `option_env!` falls back to "unknown" when the build
295	// ran outside Build.sh (e.g. plain `cargo build`).
296	// -------------------------------------------------------------------------
297	{
298		let NamedProfile = option_env!("Profile").unwrap_or("unknown");
299
300		let Workbench = option_env!("Pack").unwrap_or("Unknown");
301
302		let Bundle = option_env!("Bundle").unwrap_or("");
303
304		let Compiler = option_env!("Compiler").unwrap_or("default");
305
306		dev_log!(
307			"lifecycle",
308			"[LandFix:Profile] Active profile={} workbench={} bundle={} compiler={}",
309			NamedProfile,
310			Workbench,
311			Bundle,
312			Compiler
313		);
314	}
315
316	// -------------------------------------------------------------------------
317	// [Boot] [Runtime] Tokio runtime creation
318	// -------------------------------------------------------------------------
319	TraceStep!("[Boot] [Runtime] Building Tokio runtime...");
320
321	let Runtime = tokio::runtime::Builder::new_multi_thread()
322		.enable_all()
323		.build()
324		.expect("FATAL: Cannot build Tokio runtime.");
325
326	TraceStep!("[Boot] [Runtime] Tokio runtime built.");
327
328	Runtime.block_on(async {
329		// ---------------------------------------------------------------------
330		// [Boot] [Telemetry] Hydrate runtime env from compile-baked
331		// Constants so spawned children (Cocoon Node, Sky webview)
332		// see the same telemetry config Mountain itself was built
333		// with - even when the user invokes the bare binary without
334		// sourcing `.env.Land.PostHog`. Idempotent + debug-only.
335		// Must run BEFORE PostHogPlugin::Initialize so the client
336		// reads the same effective env as the children.
337		// ---------------------------------------------------------------------
338		crate::Binary::Build::PostHogPlugin::HydrateRuntimeEnvironment::Fn();
339
340		// ---------------------------------------------------------------------
341		// [Boot] [PostHog] Initialize telemetry client first so any
342		// error captured during the rest of boot lands in the project.
343		// No-op in release builds or when Report=false.
344		// ---------------------------------------------------------------------
345		crate::Binary::Build::PostHogPlugin::Initialize::Fn().await;
346
347		// ---------------------------------------------------------------------
348		// [Boot] [Common::Telemetry] Initialize the shared dual-pipe
349		// stack so library crates linked into Mountain (Echo, Mist,
350		// Common) emit through the same client. The HydrateRuntime
351		// Environment step above populated the env so this picks up
352		// the same Authorize/Beam/Capture values Mountain's plugin
353		// already loaded. Idempotent.
354		// ---------------------------------------------------------------------
355		CommonLibrary::Telemetry::Initialize::Fn(CommonLibrary::Telemetry::Tier::Tier::Mountain).await;
356
357		// ---------------------------------------------------------------------
358		// [Boot] [Args] CLI parsing (using CliParse module)
359		// ---------------------------------------------------------------------
360		let _WorkspaceConfigurationPath = CliParseFn();
361		let _InitialFolders:Vec<String> = vec![];
362
363		// ---------------------------------------------------------------------
364		// [Boot] [State] ApplicationState (using StateBuild module)
365		// ---------------------------------------------------------------------
366		dev_log!("lifecycle", "[Boot] [State] Building ApplicationState...");
367
368		// Create application state directly (StateBuild::Build with default config)
369		let AppState = ApplicationState::default();
370
371		// -------------------------------------------------------------------
372		// [Boot] [Workspace] Seed initial workspace folders so every extension
373		// that calls `vscode.workspace.findFiles(...)` at activation has
374		// something to walk. Precedence: --folder flags → positional dirs →
375		// Open env → CWD fallback. See
376		// CliParse::ParseWorkspaceFolders.
377		// -------------------------------------------------------------------
378		{
379			let InitialFolderPaths = crate::Binary::Initialize::CliParse::ParseWorkspaceFolders();
380			if InitialFolderPaths.is_empty() {
381				dev_log!(
382					"lifecycle",
383					"[Boot] [Workspace] No initial folders resolved - editor will open in \"no folder\" mode."
384				);
385			} else {
386				use crate::ApplicationState::DTO::WorkspaceFolderStateDTO::WorkspaceFolderStateDTO;
387				let mut Folders:Vec<WorkspaceFolderStateDTO> = Vec::new();
388				for (Index, Path) in InitialFolderPaths.iter().enumerate() {
389					let Uri = match url::Url::from_directory_path(Path) {
390						Ok(U) => U,
391						Err(()) => {
392							dev_log!(
393								"lifecycle",
394								"warn: [Boot] [Workspace] Failed to build URL for {}; skipping",
395								Path.display()
396							);
397							continue;
398						},
399					};
400					let Name = Path
401						.file_name()
402						.and_then(|N| N.to_str())
403						.map(str::to_string)
404						.unwrap_or_else(|| Path.display().to_string());
405					match WorkspaceFolderStateDTO::New(Uri, Name, Index) {
406						Ok(Dto) => Folders.push(Dto),
407						Err(Error) => {
408							dev_log!(
409								"lifecycle",
410								"warn: [Boot] [Workspace] Failed to build folder DTO for {}: {}",
411								Path.display(),
412								Error
413							);
414						},
415					}
416				}
417				if !Folders.is_empty() {
418					// Seed state directly; Cocoon is not yet spawned at this
419					// point, so there is no sidecar to notify. The initial
420					// workspace makes it to Cocoon via `InitializeExtensionHost`'s
421					// `workspace` payload during its handshake instead.
422					AppState.Workspace.SetWorkspaceFolders(Folders);
423					dev_log!(
424						"lifecycle",
425						"[Boot] [Workspace] Seeded {} workspace folder(s).",
426						InitialFolderPaths.len()
427					);
428				}
429			}
430		}
431
432		dev_log!(
433			"lifecycle",
434			"[Boot] [State] ApplicationState created with {} workspace folders.",
435			AppState.Workspace.WorkspaceFolders.lock().map(|f| f.len()).unwrap_or(0)
436		);
437
438		// Create Arc for application state to be managed by Tauri
439		let AppStateArcForClosure = Arc::new(AppState.clone());
440
441		// ---------------------------------------------------------------------
442		// [Boot] [Runtime] Scheduler handles (using RuntimeBuild module)
443		// ---------------------------------------------------------------------
444		let Scheduler = Arc::new(SchedulerBuilder::Create().Build());
445		let SchedulerForClosure = Scheduler.clone();
446		TraceStep!("[Boot] [Echo] Scheduler handles prepared.");
447
448		// ---------------------------------------------------------------------
449		// [Boot] [Localhost] Port selection (using PortSelector module)
450		// ---------------------------------------------------------------------
451		let ServerPort = SelectPort();
452		let LocalhostUrl = BuildPortUrl(ServerPort);
453
454		// ---------------------------------------------------------------------
455		// [Boot] [Logging] Log level resolution (using LogLevel module)
456		// ---------------------------------------------------------------------
457		let log_level = ResolveLogLevel();
458
459		// ---------------------------------------------------------------------
460		// [Boot] [Tauri] Builder setup (using TauriBuild module)
461		// ---------------------------------------------------------------------
462		let Builder = TauriBuildFn();
463
464		Builder
465			.plugin(LoggingPluginFn(log_level))
466			.plugin(LocalhostPluginFn(ServerPort))
467			.manage(AppStateArcForClosure.clone())
468			.setup({
469				let LocalhostUrl = LocalhostUrl.clone();
470				let ServerPortForClosure = ServerPort;
471				move |app:&mut App| {
472					dev_log!("lifecycle", "[Lifecycle] [Setup] Setup hook started.");
473					dev_log!("lifecycle", "[Lifecycle] [Setup] LocalhostUrl={}", LocalhostUrl);
474
475					// ---------------------------------------------------------
476					// [Service Registry] Initialize service registry for land:// routing
477					// ---------------------------------------------------------
478					dev_log!(
479						"lifecycle",
480						"[Lifecycle] [Setup] Initializing ServiceRegistry for land:// scheme..."
481					);
482					let service_registry = ServiceRegistryFn::new();
483					init_service_registry(service_registry.clone());
484
485					// ---------------------------------------------------------
486					// [Service Registry] Register local HTTP services
487					// ---------------------------------------------------------
488					// Register the main code editor service
489					dev_log!(
490						"lifecycle",
491						"[Lifecycle] [Setup] Registering code.land.playform.cloud service on port {}",
492						ServerPortForClosure
493					);
494					register_land_service("code.land.playform.cloud", ServerPortForClosure);
495
496					// Register API editor service (same port for now, can be separate later)
497					register_land_service("api.land.playform.cloud", ServerPortForClosure);
498
499					// Register assets editor service (same port for now, can be separate later)
500					register_land_service("assets.land.playform.cloud", ServerPortForClosure);
501
502					// Make the registry available as managed state for Tauri commands
503					app.manage(service_registry);
504					dev_log!(
505						"lifecycle",
506						"[Lifecycle] [Setup] ServiceRegistry initialized and services registered."
507					);
508
509					// ---------------------------------------------------------
510					// [DNS Server] Start the Hickory DNS server
511					// ---------------------------------------------------------
512					// The DNS server must start BEFORE any webview loads to ensure
513					// that land:// protocol_resolution is available
514					dev_log!("lifecycle", "[Lifecycle] [Setup] Starting DNS server on preferred port 5380...");
515					let dns_port = Mist::start(5380).unwrap_or_else(|e| {
516						dev_log!(
517							"lifecycle",
518							"warn: [Lifecycle] [Setup] Failed to start DNS server on port 5380: {}",
519							e
520						);
521						// Fallback to random port if preferred port fails
522						Mist::start(0).unwrap_or_else(|e| {
523							dev_log!(
524								"lifecycle",
525								"error: [Lifecycle] [Setup] Completely failed to start DNS server: {}",
526								e
527							);
528							0 // Return 0 as error indicator
529						})
530					});
531
532					if dns_port == 0 {
533						dev_log!(
534							"lifecycle",
535							"warn: [Lifecycle] [Setup] DNS server failed to start, land:// protocol will not be \
536							 available"
537						);
538					} else {
539						dev_log!(
540							"lifecycle",
541							"[Lifecycle] [Setup] DNS server started successfully on port {}",
542							dns_port
543						);
544						// Initialize DNS startup time for tracking
545						crate::Binary::Build::DnsCommands::StartupTime::init_dns_startup_time();
546					}
547
548					// Register DnsPort as managed state for Tauri commands
549					app.manage(DnsPort(dns_port));
550
551					let AppHandle = app.handle().clone();
552					TraceStep!("[Lifecycle] [Setup] AppHandle acquired.");
553
554					// ---------------------------------------------------------
555					// Setup application lifecycle through AppLifecycle module
556					// ---------------------------------------------------------
557					let AppStateArcFromClosure = AppStateArcForClosure.clone();
558
559					if let Err(e) = AppLifecycleSetup(
560						app,
561						AppHandle.clone(),
562						LocalhostUrl.clone(),
563						SchedulerForClosure.clone(),
564						AppStateArcFromClosure,
565					) {
566						dev_log!("lifecycle", "error: [Lifecycle] [Setup] Failed to setup lifecycle: {}", e);
567					}
568
569					Ok(())
570				}
571			})
572			.register_asynchronous_uri_scheme_protocol("fiddee", |_ctx, request, responder| {
573				// Implemented: delegate to synchronous scheme handler
574				let response = crate::Binary::Build::Scheme::land_scheme_handler(&request);
575				responder.respond(response);
576			})
577			.register_asynchronous_uri_scheme_protocol("vscode-file", |ctx, request, responder| {
578				// VS Code Electron workbench uses vscode-file:// to load assets.
579				// Maps to embedded frontend assets from Sky/Target.
580				let AppHandle = ctx.app_handle().clone();
581				std::thread::spawn(move || {
582					let response = crate::Binary::Build::Scheme::VscodeFileSchemeHandler(&AppHandle, &request);
583					responder.respond(response);
584				});
585			})
586			.register_asynchronous_uri_scheme_protocol("vscode-webview", |ctx, request, responder| {
587				// VS Code's `WebviewElement` wraps every extension webview in
588				// an iframe whose `src` is `vscode-webview://<authority>/index.html?...`.
589				// Without this handler the iframe stays blank and every
590				// extension that uses `webviewView` / `WebviewPanel` /
591				// `CustomEditor` (Roo Code, Claude, GitLens, custom editors)
592				// is dead. The handler serves the three-file `pre/`
593				// directory (`index.html`, `service-worker.js`, `fake.html`);
594				// extension HTML itself comes through later via the workbench's
595				// `swMessage` postMessage channel, not this scheme.
596				let AppHandle = ctx.app_handle().clone();
597				std::thread::spawn(move || {
598					let response = crate::Binary::Build::Scheme::VscodeWebviewSchemeHandler(&AppHandle, &request);
599					responder.respond(response);
600				});
601			})
602			.register_asynchronous_uri_scheme_protocol("vscode-webview-resource", |ctx, request, responder| {
603				// `vscode-webview-resource://<auth>/<path>` is the URI shape
604				// stock VS Code emits from `webview.asWebviewUri(...)`. The
605				// service worker inside `pre/index.html` would normally
606				// intercept these and proxy through the host. Land disables
607				// that SW (see Output `PatchWebviewIframeServiceWorker`)
608				// because WKWebView refuses SW registration on the
609				// `vscode-webview://` custom protocol; instead we register
610				// this scheme directly so any extension that hard-codes
611				// the URI form (or didn't go through Cocoon's `asWebviewUri`
612				// rewrite) still resolves. Strip the `<auth>` and forward
613				// the path to `VscodeFileSchemeHandler` by rewriting the
614				// URI to `vscode-file://vscode-app/<path>`.
615				let AppHandle = ctx.app_handle().clone();
616				std::thread::spawn(move || {
617					let Original = request.uri().to_string();
618					let RewrittenUri = match Original.strip_prefix("vscode-webview-resource://") {
619						Some(After) => {
620							let Rest = After.find('/').map(|I| &After[I..]).unwrap_or("/");
621							format!("vscode-file://vscode-app{}", Rest)
622						},
623						None => "vscode-file://vscode-app/".to_string(),
624					};
625					crate::dev_log!(
626						"scheme-assets",
627						"[LandFix:VscodeWebviewResource] {} -> {}",
628						Original,
629						RewrittenUri
630					);
631					let mut Builder = tauri::http::request::Request::builder().uri(&RewrittenUri);
632					for (Name, Value) in request.headers().iter() {
633						Builder = Builder.header(Name, Value);
634					}
635					let Forwarded = Builder
636						.method(request.method().clone())
637						.body(request.body().clone())
638						.unwrap_or_else(|_| request.clone());
639					let response = crate::Binary::Build::Scheme::VscodeFileSchemeHandler(&AppHandle, &Forwarded);
640					responder.respond(response);
641				});
642			})
643			.register_asynchronous_uri_scheme_protocol("vscode-resource", |ctx, request, responder| {
644				// Legacy stock-VS Code resource scheme. Same handling as
645				// `vscode-webview-resource` - rewrite to `vscode-file://`
646				// and dispatch through the existing file handler.
647				let AppHandle = ctx.app_handle().clone();
648				std::thread::spawn(move || {
649					let Original = request.uri().to_string();
650					let RewrittenUri = match Original.strip_prefix("vscode-resource://") {
651						Some(After) => {
652							let Rest = After.find('/').map(|I| &After[I..]).unwrap_or("/");
653							format!("vscode-file://vscode-app{}", Rest)
654						},
655						None => "vscode-file://vscode-app/".to_string(),
656					};
657					crate::dev_log!("scheme-assets", "[LandFix:VscodeResource] {} -> {}", Original, RewrittenUri);
658					let mut Builder = tauri::http::request::Request::builder().uri(&RewrittenUri);
659					for (Name, Value) in request.headers().iter() {
660						Builder = Builder.header(Name, Value);
661					}
662					let Forwarded = Builder
663						.method(request.method().clone())
664						.body(request.body().clone())
665						.unwrap_or_else(|_| request.clone());
666					let response = crate::Binary::Build::Scheme::VscodeFileSchemeHandler(&AppHandle, &Forwarded);
667					responder.respond(response);
668				});
669			})
670			.plugin(tauri_plugin_dialog::init())
671			.plugin(tauri_plugin_fs::init())
672			.invoke_handler(tauri::generate_handler![
673				crate::Binary::Tray::SwitchTrayIcon::SwitchTrayIcon,
674
675				crate::Binary::IPC::WorkbenchConfigurationCommand::MountainGetWorkbenchConfiguration,
676
677				Command::TreeView::GetTreeViewChildren::GetTreeViewChildren,
678
679				Command::LanguageFeature::MountainProvideHover::MountainProvideHover,
680
681				Command::LanguageFeature::MountainProvideCompletions::MountainProvideCompletions,
682
683				Command::LanguageFeature::MountainProvideDefinition::MountainProvideDefinition,
684
685				Command::LanguageFeature::MountainProvideReferences::MountainProvideReferences,
686
687				Command::SourceControlManagement::GetAllSourceControlManagementState::GetAllSourceControlManagementState,
688
689				Command::Keybinding::GetResolvedKeybinding::GetResolvedKeybinding,
690
691				Track::FrontendCommand::DispatchFrontendCommand::DispatchFrontendCommand,
692
693				Track::UIRequest::ResolveUIRequest::ResolveUIRequest,
694
695				Track::Webview::MountainWebviewPostMessageFromGuest::MountainWebviewPostMessageFromGuest,
696
697				crate::Binary::IPC::MessageReceiveCommand::MountainIPCReceiveMessage,
698
699				crate::Binary::IPC::StatusGetCommand::MountainIPCGetStatus,
700
701				crate::Binary::IPC::InvokeCommand::MountainIPCInvoke,
702
703				crate::Binary::IPC::WindConfigurationCommand::MountainGetWindDesktopConfiguration,
704
705				crate::Binary::IPC::ConfigurationUpdateCommand::MountainUpdateConfigurationFromWind,
706
707				crate::Binary::IPC::ConfigurationSyncCommand::MountainSynchronizeConfiguration,
708
709				crate::Binary::IPC::ConfigurationStatusCommand::MountainGetConfigurationStatus,
710
711				crate::Binary::IPC::IPCStatusCommand::MountainGetIPCStatus,
712
713				crate::Binary::IPC::IPCStatusHistoryCommand::MountainGetIPCStatusHistory,
714
715				crate::Binary::IPC::IPCStatusReportingStartCommand::MountainStartIPCStatusReporting,
716
717				crate::Binary::IPC::PerformanceStatsCommand::MountainGetPerformanceStats,
718
719				crate::Binary::IPC::CacheStatsCommand::MountainGetCacheStats,
720
721				crate::Binary::IPC::CollaborationSessionCommand::MountainCreateCollaborationSession,
722
723				crate::Binary::IPC::CollaborationSessionCommand::MountainGetCollaborationSessions,
724
725				crate::Binary::IPC::DocumentSyncCommand::MountainAddDocumentForSync,
726
727				crate::Binary::IPC::DocumentSyncCommand::MountainGetSyncStatus,
728
729				crate::Binary::IPC::UpdateSubscriptionCommand::MountainSubscribeToUpdates,
730
731				crate::Binary::IPC::ConfigurationDataCommand::GetConfigurationData,
732
733				crate::Binary::IPC::ConfigurationDataCommand::SaveConfigurationData,
734
735				crate::Binary::IPC::WorkspaceFolderCommand::MountainWorkspaceOpenFolder,
736
737				crate::Binary::IPC::WorkspaceFolderCommand::MountainWorkspaceListFolders,
738
739				crate::Binary::IPC::WorkspaceFolderCommand::MountainWorkspaceCloseAllFolders,
740
741				crate::Binary::Build::DnsCommands::dns_get_server_info::dns_get_server_info,
742
743				crate::Binary::Build::DnsCommands::dns_get_zone_info::dns_get_zone_info,
744
745				crate::Binary::Build::DnsCommands::dns_get_forward_allowlist::dns_get_forward_allowlist,
746
747				crate::Binary::Build::DnsCommands::dns_get_health_status::dns_get_health_status,
748
749				crate::Binary::Build::DnsCommands::dns_resolve::dns_resolve,
750
751				crate::Binary::Build::DnsCommands::dns_test_resolution::dns_test_resolution,
752
753				crate::Binary::Build::DnsCommands::dns_health_check::dns_health_check,
754
755				// Process commands (direct Tauri invoke from ProcessPolyfill)
756				crate::Binary::IPC::ProcessCommand::process_get_exec_path::process_get_exec_path,
757
758				crate::Binary::IPC::ProcessCommand::process_get_platform::process_get_platform,
759
760				crate::Binary::IPC::ProcessCommand::process_get_arch::process_get_arch,
761
762				crate::Binary::IPC::ProcessCommand::process_get_pid::process_get_pid,
763
764				crate::Binary::IPC::ProcessCommand::process_get_shell_env::process_get_shell_env,
765
766				crate::Binary::IPC::ProcessCommand::process_get_memory_info::process_get_memory_info,
767
768				// Health check commands (direct Tauri invoke from SharedProcessProxy)
769				crate::Binary::IPC::HealthCommand::cocoon_extension_host_health::cocoon_extension_host_health,
770
771				crate::Binary::IPC::HealthCommand::cocoon_search_service_health::cocoon_search_service_health,
772
773				crate::Binary::IPC::HealthCommand::cocoon_debug_service_health::cocoon_debug_service_health,
774
775				crate::Binary::IPC::HealthCommand::shared_process_service_health::shared_process_service_health,
776
777				crate::Binary::IPC::RenderDevLogCommand::RenderDevLog,
778
779				// LAND-PATCH B7-S6 P14.5: Vine notification broadcast
780				// subscription. `vine_subscribe_notifications` opens a
781				// Tauri Channel that drains the process-wide
782				// `Vine::Client` broadcast into the webview; Effect-TS
783				// `VineNotificationsLive` Layer wraps it as a
784				// `Stream<NotificationFrame>`. `vine_subscriber_count`
785				// is a diagnostic for verifying registrations didn't
786				// leak across reloads.
787				crate::Binary::IPC::VineSubscribeCommand::vine_subscribe_notifications,
788
789				crate::Binary::IPC::VineSubscribeCommand::vine_subscriber_count,
790			])
791			.build(tauri::generate_context!())
792			.expect("FATAL: Error while building Mountain Tauri application")
793			.run(move |app_handle:&tauri::AppHandle, event:tauri::RunEvent| {
794				// Debug-only: log selected lifecycle events
795				if cfg!(debug_assertions) {
796					match &event {
797						RunEvent::MainEventsCleared => {},
798						RunEvent::WindowEvent { .. } => {},
799						_ => dev_log!("lifecycle", "[Lifecycle] [RunEvent] {:?}", event),
800					}
801				}
802
803				if let RunEvent::ExitRequested { api, .. } = event {
804					// Shutdown runs once. The graceful path ends with
805					// `app_handle.exit(0)`, which Tauri re-delivers as a
806					// second `ExitRequested { code: Some(0) }`. On re-entry
807					// we must NOT `prevent_exit` or spawn the shutdown task
808					// again - Cocoon has already been SIGKILLed and the
809					// second pass would log spurious "tcp connect error"
810					// warnings trying to notify a dead sidecar.
811					static SHUTTING_DOWN:AtomicBool = AtomicBool::new(false);
812					if SHUTTING_DOWN.swap(true, Ordering::SeqCst) {
813						return;
814					}
815
816					dev_log!(
817						"lifecycle",
818						"warn: [Lifecycle] [Shutdown] Exit requested. Starting graceful shutdown..."
819					);
820					api.prevent_exit();
821
822					let SchedulerHandle = Scheduler.clone();
823					let app_handle_clone = app_handle.clone();
824
825					tokio::spawn(async move {
826						dev_log!("lifecycle", "[Lifecycle] [Shutdown] Shutting down ApplicationRunTime...");
827						let _ = RuntimeShutdownFn(&app_handle_clone).await;
828
829						dev_log!("lifecycle", "[Lifecycle] [Shutdown] Stopping Echo scheduler...");
830						let _ = SchedulerShutdownFn(SchedulerHandle).await;
831
832						dev_log!("lifecycle", "[Lifecycle] [Shutdown] Done. Exiting process.");
833						app_handle_clone.exit(0);
834					});
835				}
836			});
837
838		dev_log!("lifecycle", "[Lifecycle] [Exit] Mountain application has shut down.");
839	});
840}