Skip to main content

Mountain/IPC/
ConfigurationBridge.rs

1//! # Configuration Bridge - Bidirectional Configuration Synchronization
2//!
3//! **File Responsibilities:**
4//! This module manages bidirectional synchronization of configuration between
5//! Mountain's Rust backend and Wind's TypeScript frontend. It ensures
6//! configuration consistency across the entire CodeEditorLand ecosystem while
7//! handling conflicts and updates gracefully.
8//!
9//! **Architectural Role in Wind-Mountain Connection:**
10//!
11//! The ConfigurationBridge is the synchronization layer that:
12//!
13//! 1. **Translates Configuration Formats:** Converts between Mountain's
14//!    internal config structure and Wind's desktop configuration interface
15//! 2. **Bidirectional Sync:** Maintains consistency in both directions
16//!    (Wind→Mountain and Mountain→Wind)
17//! 3. **Conflict Resolution:** Handles merge conflicts when multiple sources
18//!    update configuration simultaneously
19//! 4. **Validation:** Ensures all configuration changes are valid before
20//!    applying
21//! 5. **Identity Management:** Generates unique machine and session IDs for
22//!    multi- instance scenarios
23//!
24//! **Bidirectional Synchronization Flow:**
25//!
26//! **Mountain → Wind Sync:**
27//! ```
28//! Mountain Services (Internal Config)
29//!       |
30//!       | get_mountain_configuration()
31//!       v
32//! ConfigurationBridge
33//!       |
34//!       | WindServiceAdapter.convert_to_wind_configuration()
35//!       v
36//! Wind Desktop Configuration Format
37//!       |
38//!       | send_configuration_to_wind()
39//!       v
40//! Wind Frontend (via IPC)
41//! ```
42//!
43//! **Wind → Mountain Sync:**
44//! ```
45//! Wind Frontend (User Changes)
46//!       |
47//!       | WindConfigurationChange()
48//!       v
49//! ConfigurationBridge
50//!       |
51//!       | convert_to_mountain_configuration()
52//!       v
53//! Mountain Configuration Format
54//!       |
55//!       | update_mountain_configuration()
56//!       v
57//! Mountain Services (Internal Config)
58//! ```
59//!
60//! **Configuration Bridge Features:**
61//!
62//! **1. Format Translation:**
63//! - Mountain's internal JSON structure → Wind's desktop configuration
64//!   interface
65//! - Handles nested configuration objects
66//! - Type conversion between TypeScript and Rust types
67//!
68//! **2. Conflict Resolution Strategy:**
69//!
70//! **Current Implementation (Basic):**
71//! - Last-write-wins (most recent update takes precedence)
72//! - Configuration is validated before applying
73//! - Invalid changes are rejected entirely
74//!
75//! **Advanced Conflict Resolution (Future Enhancement):**
76//! - Detect conflicts based on modification timestamps
77//! - Provide conflict metadata (source, timestamp, value)
78//! - Support three-way merge strategies:
79//!   - **Ours:** Keep Mountain's version
80//!   - **Theirs:** Use Wind's version
81//!   - **Merge:** Attempt intelligent merge
82//! - Conflict UI prompts in Wind for user resolution
83//!
84//! **3. Validation Rules:**
85//!
86//! **Type Validation:**
87//! - `zoom_level`: Number, range -8.0 to 9.0
88//! - `font_size`: Number, range 6.0 to 100.0
89//! - `is_packaged`: Boolean
90//! - `theme`, `platform`, `arch`: String, non-empty
91//! - All other values: Not null
92//!
93//! **Key Validation:**
94//! - Configuration keys must not be empty or whitespace
95//! - Reserved keys cannot be modified
96//! - Nested paths use dot notation (e.g., "editor.theme")
97//!
98//! **Value Validation:**
99//! - Ranges checked for numeric values
100//! - Enum validation for predefined options
101//! - Pattern validation for string values (URLs, paths)
102//!
103//! **4. Identity Management:**
104//!
105//! **Machine ID Generation (Microsoft-Inspired):**
106//! - **macOS:** Get system serial number via `system_profiler`
107//! - **Windows:** Get machine UUID via `wmic csproduct get UUID`
108//! - **Linux:** Read from `/etc/machine-id` or `/var/lib/dbus/machine-id`
109//! - **Fallback:** Hash hostname + timestamp
110//!
111//! **Session ID Generation (Secure):**
112//! - Combine timestamp, random number, and process ID
113//! - Hash with SHA-256
114//! - Use first 16 characters of hex digest
115//! - Format: `session-{16-char-hash}`
116//!
117//! **5. Bidirectional Sync Triggers:**
118//!
119//! **Triggers for Mountain → Wind:**
120//! - Configuration changes from Mountain services
121//! - Periodic sync interval (configurable)
122//! - Manual sync request from Mountain
123//!
124//! **Triggers for Wind → Mountain:**
125//! - User changes configuration in Wind UI
126//! - Settings panel updates
127//! - Extension configuration changes
128//! - Command palette configuration commands
129//!
130//! **Key Structures:**
131//!
132//! **ConfigurationBridge:**
133//! Main synchronization orchestrator
134//! - `get_wind_desktop_configuration()` - Get config in Wind format
135//! - `update_configuration_from_wind()` - Apply Wind's config changes
136//! - `synchronize_configuration()` - Force bidirectional sync
137//! - `get_configuration_status()` - Get sync status info
138//!
139//! **ConfigurationStatus:**
140//! Current synchronization state
141//! - `is_valid` - Whether configuration is valid
142//! - `last_sync` - Timestamp of last successful sync
143//! - `configuration_keys` - List of all configuration keys
144//!
145//! **Tauri Commands:**
146//!
147//! The module provides Tauri commands for Wind to invoke:
148//!
149//! - `mountain_get_wind_desktop_configuration` - Get config for Wind UI
150//! - `get_configuration_data` - Get all configuration data
151//! - `save_configuration_data` - Save configuration from Wind
152//! - `mountain_update_configuration_from_wind` - Update config from Wind
153//! - `mountain_synchronize_configuration` - Force sync
154//! - `mountain_get_configuration_status` - Get sync status
155//!
156//! **Configuration Flow Examples:**
157//!
158//! **Example 1: Wind Initializing**
159//! ```typescript
160//! // Wind startup
161//! const config = await invoke('mountain_get_wind_desktop_configuration');
162//! applyConfiguration(config);
163//! ```
164//!
165//! **Example 2: User Changes Theme**
166//! ```typescript
167//! // User changes theme in Wind UI
168//! const newConfig = { theme: 'dark', 'editor.fontSize': 14 };
169//! await invoke('save_configuration_data', newConfig);
170//! ```
171//!
172//! **Example 3: Mountain Updates Setting**
173//! ```text
174//! // Mountain service updates configuration
175//! let bridge = ConfigurationBridge::new(runtime);
176//! bridge.synchronize_configuration().await?;
177//!
178//! // Result: Wind UI automatically updates via IPC event
179//! ```
180//!
181//! **Error Handling Strategy:**
182//!
183//! **Configuration Validation Errors:**
184//! - Reject entire invalid configuration
185//! - Return detailed validation error messages
186//! - List which keys/values failed validation
187//!
188//! **Format Conversion Errors:**
189//! - Log conversion errors with field names
190//! - Attempt graceful fallback for missing fields
191//! - Use defaults for conversion failures
192//!
193//! **Sync Errors:**
194//! - Log sync failures with timestamps
195//! - Queue sync for retry on transient errors
196//! - Alert monitoring system on persistent failures
197//!
198//! **Integration with Other Modules:**
199//!
200//! **WindServiceAdapters:**
201//! - Uses `WindServiceAdapter.convert_to_wind_configuration()`
202//! - Depends on `WindDesktopConfiguration` structure
203//!
204//! **TauriIPCServer:**
205//! - Sends configuration updates via IPC events
206//! - Receives configuration changes from Wind
207//!
208//! **Mountain Configuration Service:**
209//! - Delegates to `ConfigurationProvider` trait
210//! - Uses `ConfigurationTarget` for scoping
211//!
212//! **Best Practices:**
213//!
214//! 1. **Always Validate:** Never apply configuration without validation
215//! 2. **Atomic Updates:** Apply entire configuration atomically
216//! 3. **Versioning:** Consider adding configuration versioning
217//! 4. **Change Logging:** Log all configuration changes for audit
218//! 5. **Fallback Support:** Provide sensible defaults for all settings
219//! 6. **Conflict Detection:** Implement proper conflict detection before merges
220
221use std::sync::Arc;
222
223use serde::{Deserialize, Serialize};
224use tauri::Manager;
225// Type aliases for Configuration DTOs to simplify usage
226use CommonLibrary::Configuration::DTO::{
227	ConfigurationOverridesDTO as ConfigurationOverridesDTOModule,
228	ConfigurationTarget as ConfigurationTargetModule,
229};
230
231type ConfigurationOverridesDTO = ConfigurationOverridesDTOModule::ConfigurationOverridesDTO;
232
233type ConfigurationTarget = ConfigurationTargetModule::ConfigurationTarget;
234
235use CommonLibrary::{Configuration::ConfigurationProvider::ConfigurationProvider, Environment::Requires::Requires};
236use sha2::Digest;
237
238use crate::{
239	IPC::WindServiceAdapters::{
240		WindDesktopConfiguration::Struct as WindDesktopConfiguration,
241		WindServiceAdapter::Struct as WindServiceAdapter,
242	},
243	RunTime::ApplicationRunTime::ApplicationRunTime,
244	dev_log,
245};
246
247/// Configuration bridge that handles Wind's desktop configuration needs
248pub struct ConfigurationBridge {
249	runtime:Arc<ApplicationRunTime>,
250}
251
252impl ConfigurationBridge {
253	/// Create a new configuration bridge
254	pub fn new(runtime:Arc<ApplicationRunTime>) -> Self {
255		dev_log!("config", "[ConfigurationBridge] Creating configuration bridge");
256
257		Self { runtime }
258	}
259
260	/// Get Wind-compatible desktop configuration
261	pub async fn get_wind_desktop_configuration(&self) -> Result<WindDesktopConfiguration, String> {
262		dev_log!("config", "[ConfigurationBridge] Getting Wind desktop configuration");
263
264		// Get the current Mountain configuration
265		let mountain_config = self.get_mountain_configuration().await?;
266
267		// Convert to Wind format using the service adapter
268		let service_adapter = WindServiceAdapter::new(self.runtime.clone());
269
270		let wind_config = service_adapter.convert_to_wind_configuration(mountain_config).await?;
271
272		dev_log!("config", "[ConfigurationBridge] Wind configuration ready");
273
274		Ok(wind_config)
275	}
276
277	/// Update configuration from Wind frontend
278	pub async fn update_configuration_from_wind(&self, wind_config:WindDesktopConfiguration) -> Result<(), String> {
279		dev_log!("config", "[ConfigurationBridge] Updating configuration from Wind");
280
281		// Convert Wind configuration to Mountain format
282		let mountain_config = self.convert_to_mountain_configuration(wind_config).await?;
283
284		// Update Mountain's configuration system
285		self.update_mountain_configuration(mountain_config).await?;
286
287		dev_log!("config", "[ConfigurationBridge] Configuration updated successfully");
288
289		Ok(())
290	}
291
292	/// Get Mountain's current configuration
293	async fn get_mountain_configuration(&self) -> Result<serde_json::Value, String> {
294		dev_log!("config", "[ConfigurationBridge] Getting Mountain configuration");
295
296		let config_provider:Arc<dyn ConfigurationProvider> = self.runtime.Environment.Require();
297
298		let config = config_provider
299			.GetConfigurationValue(None, ConfigurationOverridesDTO::default())
300			.await
301			.map_err(|e| format!("Failed to get Mountain configuration: {}", e))?;
302
303		Ok(config)
304	}
305
306	/// Update Mountain's configuration
307	async fn update_mountain_configuration(&self, config:serde_json::Value) -> Result<(), String> {
308		dev_log!("config", "[ConfigurationBridge] Updating Mountain configuration");
309
310		// Validate configuration before updating
311		if !self.validate_configuration(&config) {
312			return Err("Invalid configuration data".to_string());
313		}
314
315		let config_provider:Arc<dyn ConfigurationProvider> = self.runtime.Environment.Require();
316
317		// Update configuration values
318		if let Some(obj) = config.as_object() {
319			for (key, value) in obj {
320				config_provider
321					.UpdateConfigurationValue(
322						key.clone(),
323						value.clone(),
324						ConfigurationTarget::User,
325						ConfigurationOverridesDTO::default(),
326						None,
327					)
328					.await
329					.map_err(|e| format!("Failed to update configuration key {}: {}", key, e))?;
330			}
331		}
332
333		Ok(())
334	}
335
336	/// Validate configuration data
337	fn validate_configuration(&self, config:&serde_json::Value) -> bool {
338		// Basic validation: config must be an object
339		if !config.is_object() {
340			return false;
341		}
342
343		// Validate individual configuration values
344		if let Some(obj) = config.as_object() {
345			for (key, value) in obj {
346				// Key validation
347				if key.trim().is_empty() {
348					return false;
349				}
350
351				// Value type validation
352				match key.as_str() {
353					"zoom_level" | "font_size" => {
354						if let Some(num) = value.as_f64() {
355							if key == "zoom_level" && (num < -8.0 || num > 9.0) {
356								return false;
357							}
358
359							if key == "font_size" && (num < 6.0 || num > 100.0) {
360								return false;
361							}
362						} else {
363							return false;
364						}
365					},
366
367					"is_packaged" | "enable_feature" => {
368						if !value.is_boolean() {
369							return false;
370						}
371					},
372
373					"theme" | "platform" | "arch" => {
374						if !value.is_string() || value.as_str().unwrap().trim().is_empty() {
375							return false;
376						}
377					},
378
379					_ => {
380						// Default validation: value must not be null
381						if value.is_null() {
382							return false;
383						}
384					},
385				}
386			}
387		}
388
389		true
390	}
391
392	/// Convert Wind configuration to Mountain format
393	async fn convert_to_mountain_configuration(
394		&self,
395
396		wind_config:WindDesktopConfiguration,
397	) -> Result<serde_json::Value, String> {
398		dev_log!("config", "[ConfigurationBridge] Converting Wind config to Mountain format");
399
400		let machine_id = self.generate_machine_id().await.unwrap_or_else(|e| {
401			dev_log!("config", "warn: [ConfigurationBridge] Failed to generate machine ID: {}", e);
402
403			"wind-machine-fallback".to_string()
404		});
405
406		let session_id = self.generate_session_id().await.unwrap_or_else(|e| {
407			dev_log!("config", "warn: [ConfigurationBridge] Failed to generate session ID: {}", e);
408
409			"wind-session-fallback".to_string()
410		});
411
412		let mountain_config = serde_json::json!({
413			"window_id": wind_config.window_id.to_string(),
414			"machine_id": machine_id,
415			"session_id": session_id,
416			"log_level": wind_config.log_level,
417			"app_root": wind_config.app_root,
418			"user_data_dir": wind_config.user_data_path,
419			"tmp_dir": wind_config.temp_path,
420			"platform": wind_config.platform,
421			"arch": wind_config.arch,
422			"zoom_level": wind_config.zoom_level.unwrap_or(0.0),
423			"backup_path": wind_config.backup_path.unwrap_or_default(),
424			"home_dir": wind_config.profiles.home,
425			"is_packaged": wind_config.is_packaged,
426		});
427
428		Ok(mountain_config)
429	}
430
431	/// Synchronize configuration between Mountain and Wind
432	pub async fn synchronize_configuration(&self) -> Result<(), String> {
433		dev_log!("config", "[ConfigurationBridge] Synchronizing configuration");
434
435		// Get Mountain's current configuration
436		let mountain_config = self.get_mountain_configuration().await?;
437
438		// Convert to Wind format
439		let service_adapter = WindServiceAdapter::new(self.runtime.clone());
440
441		let wind_config = service_adapter.convert_to_wind_configuration(mountain_config).await?;
442
443		// Send configuration to Wind via IPC
444		self.send_configuration_to_wind(wind_config).await?;
445
446		dev_log!("config", "[ConfigurationBridge] Configuration synchronized");
447
448		Ok(())
449	}
450
451	/// Send configuration to Wind frontend via IPC
452	async fn send_configuration_to_wind(&self, config:WindDesktopConfiguration) -> Result<(), String> {
453		dev_log!("config", "[ConfigurationBridge] Sending configuration to Wind");
454
455		// Get the IPC server
456		if let Some(ipc_server) = self
457			.runtime
458			.Environment
459			.ApplicationHandle
460			.try_state::<crate::IPC::TauriIPCServer_Old::TauriIPCServer>()
461		{
462			let config_json =
463				serde_json::to_value(config).map_err(|e| format!("Failed to serialize configuration: {}", e))?;
464
465			ipc_server
466				.send("configuration:update", config_json)
467				.await
468				.map_err(|e| format!("Failed to send configuration to Wind: {}", e))?;
469		} else {
470			return Err("IPC Server not found".to_string());
471		}
472
473		Ok(())
474	}
475
476	/// Handle configuration changes from Wind
477	pub async fn WindConfigurationChange(&self, new_config:serde_json::Value) -> Result<(), String> {
478		dev_log!("config", "[ConfigurationBridge] Handling Wind configuration change");
479
480		// Parse Wind configuration
481		let wind_config:WindDesktopConfiguration =
482			serde_json::from_value(new_config).map_err(|e| format!("Failed to parse Wind configuration: {}", e))?;
483
484		// Update Mountain configuration
485		self.update_configuration_from_wind(wind_config).await?;
486
487		dev_log!("config", "[ConfigurationBridge] Wind configuration change handled");
488
489		Ok(())
490	}
491
492	/// Get configuration status
493	pub async fn get_configuration_status(&self) -> Result<ConfigurationStatus, String> {
494		dev_log!("config", "[ConfigurationBridge] Getting configuration status");
495
496		let mountain_config = self.get_mountain_configuration().await?;
497
498		let is_valid = !mountain_config.is_null();
499
500		let status = ConfigurationStatus {
501			is_valid,
502
503			last_sync:std::time::SystemTime::now()
504				.duration_since(std::time::UNIX_EPOCH)
505				.unwrap_or_default()
506				.as_millis() as u64,
507
508			configuration_keys:if let Some(obj) = mountain_config.as_object() {
509				obj.keys().map(|k| k.clone()).collect()
510			} else {
511				Vec::new()
512			},
513		};
514
515		Ok(status)
516	}
517
518	/// Generate unique machine ID using advanced Microsoft-inspired patterns
519	async fn generate_machine_id(&self) -> Result<String, String> {
520		// IMPLEMENTATION: Multi-platform machine ID generation
521		#[cfg(target_os = "macos")]
522		{
523			use std::process::Command;
524
525			// Get macOS serial number using system_profiler
526			let result = Command::new("system_profiler")
527				.arg("SPHardwareDataType")
528				.arg("-json")
529				.output()
530				.map_err(|e| format!("Failed to execute system_profiler: {}", e))?;
531
532			if result.status.success() {
533				let output_str = String::from_utf8_lossy(&result.stdout);
534
535				if let Ok(json) = serde_json::from_str::<serde_json::Value>(&output_str) {
536					if let Some(serial) = json["SPHardwareDataType"][0]["serial_number"].as_str() {
537						return Ok(format!("mac-{}", serial));
538					}
539				}
540			}
541		}
542
543		#[cfg(target_os = "windows")]
544		{
545			use std::process::Command;
546
547			// Get Windows machine ID using wmic
548			let result = Command::new("wmic")
549				.arg("csproduct")
550				.arg("get")
551				.arg("UUID")
552				.output()
553				.map_err(|e| format!("Failed to execute wmic: {}", e))?;
554
555			if result.status.success() {
556				let output_str = String::from_utf8_lossy(&result.stdout);
557
558				let lines:Vec<&str> = output_str.lines().collect();
559
560				if lines.len() > 1 {
561					let uuid = lines[1].trim();
562
563					if !uuid.is_empty() {
564						return Ok(format!("win-{}", uuid));
565					}
566				}
567			}
568		}
569
570		#[cfg(target_os = "linux")]
571		{
572			use std::fs;
573
574			// Try to read machine-id from /etc/machine-id
575			if let Ok(content) = fs::read_to_string("/etc/machine-id") {
576				let machine_id = content.trim();
577
578				if !machine_id.is_empty() {
579					return Ok(format!("linux-{}", machine_id));
580				}
581			}
582
583			// Fallback to /var/lib/dbus/machine-id
584			if let Ok(content) = fs::read_to_string("/var/lib/dbus/machine-id") {
585				let machine_id = content.trim();
586
587				if !machine_id.is_empty() {
588					return Ok(format!("linux-{}", machine_id));
589				}
590			}
591		}
592
593		// Fallback: Generate a unique ID based on hostname and timestamp
594		let hostname = hostname::get()
595			.map_err(|e| format!("Failed to get hostname: {}", e))?
596			.to_string_lossy()
597			.to_string();
598
599		let timestamp = std::time::SystemTime::now()
600			.duration_since(std::time::UNIX_EPOCH)
601			.unwrap_or_default()
602			.as_millis();
603
604		Ok(format!("fallback-{}-{}", hostname, timestamp))
605	}
606
607	/// Generate unique session ID with Microsoft-inspired security patterns
608	async fn generate_session_id(&self) -> Result<String, String> {
609		use std::time::{SystemTime, UNIX_EPOCH};
610
611		// IMPLEMENTATION: Secure session ID generation
612		let random_part:u64 = rand::random();
613
614		let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_millis();
615
616		// Get process ID for additional uniqueness
617		let process_id = std::process::id();
618
619		// Create a hash-based session ID
620		let session_data = format!("{}:{}:{}", timestamp, random_part, process_id);
621
622		let mut hasher = sha2::Sha256::new();
623
624		hasher.update(session_data.as_bytes());
625
626		let result = hasher.finalize();
627
628		// Convert to hex string and take first 16 characters. sha2 0.11
629		// dropped the `LowerHex` impl from `Digest::finalize()`'s output
630		// (now `hybrid_array::Array`); `hex::encode` produces the same
631		// lowercase-hex string the old `format!("{:x}", …)` emitted.
632		let hex_string = hex::encode(result);
633
634		let session_id = hex_string.chars().take(16).collect::<String>();
635
636		dev_log!("config", "[ConfigurationBridge] Generated session ID: {}", session_id);
637
638		Ok(format!("session-{}", session_id))
639	}
640}
641
642/// Configuration status structure
643#[derive(Debug, Clone, Serialize, Deserialize)]
644pub struct ConfigurationStatus {
645	pub is_valid:bool,
646
647	pub last_sync:u64,
648
649	pub configuration_keys:Vec<String>,
650}
651
652/// Tauri command to get Wind desktop configuration
653#[tauri::command]
654pub async fn mountain_get_wind_desktop_configuration(
655	app_handle:tauri::AppHandle,
656) -> Result<WindDesktopConfiguration, String> {
657	dev_log!("config", "[ConfigurationBridge] Tauri command: get_wind_desktop_configuration");
658
659	if let Some(runtime) = app_handle.try_state::<Arc<ApplicationRunTime>>() {
660		let bridge = ConfigurationBridge::new(runtime.inner().clone());
661
662		bridge.get_wind_desktop_configuration().await
663	} else {
664		Err("ApplicationRunTime not found".to_string())
665	}
666}
667
668/// Tauri command to get configuration data for Wind frontend
669#[tauri::command]
670pub async fn get_configuration_data(app_handle:tauri::AppHandle) -> Result<serde_json::Value, String> {
671	dev_log!("config", "[ConfigurationBridge] Tauri command: get_configuration_data");
672
673	if let Some(runtime) = app_handle.try_state::<Arc<ApplicationRunTime>>() {
674		let bridge = ConfigurationBridge::new(runtime.inner().clone());
675
676		// Get Mountain's current configuration
677		let mountain_config = bridge.get_mountain_configuration().await?;
678
679		// Convert to Wind format
680		let config_data = serde_json::json!({
681			"application": mountain_config.clone(),
682			"workspace": mountain_config.clone(),
683			"profile": mountain_config.clone()
684		});
685
686		dev_log!("config", "[ConfigurationBridge] Configuration data retrieved successfully");
687
688		Ok(config_data)
689	} else {
690		Err("ApplicationRunTime not found".to_string())
691	}
692}
693
694/// Tauri command to save configuration data from Wind frontend
695#[tauri::command]
696pub async fn save_configuration_data(app_handle:tauri::AppHandle, config_data:serde_json::Value) -> Result<(), String> {
697	dev_log!("config", "[ConfigurationBridge] Tauri command: save_configuration_data");
698
699	if let Some(runtime) = app_handle.try_state::<Arc<ApplicationRunTime>>() {
700		let bridge = ConfigurationBridge::new(runtime.inner().clone());
701
702		// Update Mountain configuration with the new data
703		bridge.update_mountain_configuration(config_data).await?;
704
705		dev_log!("config", "[ConfigurationBridge] Configuration data saved successfully");
706
707		Ok(())
708	} else {
709		Err("ApplicationRunTime not found".to_string())
710	}
711}
712
713/// Tauri command to update configuration from Wind
714#[tauri::command]
715pub async fn mountain_update_configuration_from_wind(
716	app_handle:tauri::AppHandle,
717
718	config:serde_json::Value,
719) -> Result<(), String> {
720	dev_log!("config", "[ConfigurationBridge] Tauri command: update_configuration_from_wind");
721
722	if let Some(runtime) = app_handle.try_state::<Arc<ApplicationRunTime>>() {
723		let bridge = ConfigurationBridge::new(runtime.inner().clone());
724
725		bridge.WindConfigurationChange(config).await
726	} else {
727		Err("ApplicationRunTime not found".to_string())
728	}
729}
730
731/// Tauri command to synchronize configuration
732#[tauri::command]
733pub async fn mountain_synchronize_configuration(app_handle:tauri::AppHandle) -> Result<serde_json::Value, String> {
734	dev_log!("config", "[ConfigurationBridge] Tauri command: synchronize_configuration");
735
736	if let Some(runtime) = app_handle.try_state::<Arc<ApplicationRunTime>>() {
737		let bridge = ConfigurationBridge::new(runtime.inner().clone());
738
739		bridge
740			.synchronize_configuration()
741			.await
742			.map(|_| serde_json::json!({ "status": "success" }))
743	} else {
744		Err("ApplicationRunTime not found".to_string())
745	}
746}
747
748/// Tauri command to get configuration status
749#[tauri::command]
750pub async fn mountain_get_configuration_status(app_handle:tauri::AppHandle) -> Result<serde_json::Value, String> {
751	dev_log!("config", "[ConfigurationBridge] Tauri command: get_configuration_status");
752
753	if let Some(runtime) = app_handle.try_state::<Arc<ApplicationRunTime>>() {
754		let bridge = ConfigurationBridge::new(runtime.inner().clone());
755
756		bridge
757			.get_configuration_status()
758			.await
759			.map(|status| serde_json::to_value(status).unwrap_or(serde_json::Value::Null))
760	} else {
761		Err("ApplicationRunTime not found".to_string())
762	}
763}