Skip to main content

Mountain/Track/Effect/
CreateEffectForRequest.rs

1#![allow(unused_imports)]
2
3//! # CreateEffectForRequest (Track)
4//!
5//! ## RESPONSIBILITIES
6//!
7//! This module provides the central routing table that maps string-based
8//! commands/RPC methods to typed effects. It creates MappedEffect (type-erased
9//! async closures) for dispatch execution and integrates with the effect system
10//! (ActionEffect) and provider traits. Some operations use direct provider
11//! calls for performance.
12//!
13//! ### Core Functions:
14//! - Map string-based method names to effect constructors
15//! - Create MappedEffect (boxed closures) for execution
16//! - Support direct provider calls for hot paths
17//! - Handle parameter deserialization and validation
18//!
19//! ## ARCHITECTURAL ROLE
20//!
21//! CreateEffectForRequest acts as the **effect mapper** in Track's dispatch
22//! system:
23//!
24//! ```text
25//! Dispatch Logic ──► CreateEffectForRequest (Match) ──► MappedEffect ──► ApplicationRunTime Execution
26//! ```
27//!
28//! ## KEY COMPONENTS
29//!
30//! - **Fn**: Main effect creation function (pub fn Fn<R:Runtime>)
31//! - **MappedEffect**: Type alias for boxed async closure (imported from
32//!   MappedEffect module)
33//!
34//! ## ERROR HANDLING
35//!
36//! - All effects return Result<Value, String> (serializable errors for IPC)
37//! - Parameter validation with descriptive error messages
38//! - Unknown command handling returns error instead of panic
39//! - Serialization/deserialization errors caught and reported
40//! - Provider errors propagate with context
41//!
42//! ## LOGGING
43//!
44//! - Unknown commands are logged at warn level
45//! - Log format: "[`CreateEffectForRequest`] Unknown method: {}"
46//!
47//! ## PERFORMANCE CONSIDERATIONS
48//!
49//! - Effect creation is cheap: match + constructor call + box
50//! - Direct provider calls avoid allocation (for hot paths)
51//! - TODO: Consider implementing an effect pool to cache frequently created
52//!   effects
53//! - TODO: Add configurable command timeouts per command type and rate limiting
54//!
55//! ## DIRECT PROVIDER CALLS
56//!
57//! Some operations bypass the effect system for performance:
58//! - Configuration: `GetConfiguration`, `UpdateConfiguration`
59//! - Diagnostics: `SetDiagnostics`, `ClearDiagnostics`
60//! - Language Features: `ProvideHover`, `ProvideCompletions`, etc.
61//! - Terminal: direct text send/receive
62//! - Why? Avoid effect overhead for high-frequency operations
63//!
64//! ## SUPPORTED COMMAND CATEGORIES
65//!
66//! **Commands**: Execute, GetAll, Register
67//! **Configuration**: Inspect, Update
68//! **Documents**: Save, SaveAs
69//! **FileSystem**: ReadFile, WriteFile, ReadDirectory
70//! **Debug**: Start, RegisterConfigurationProvider
71//! **Diagnostics**: Set, Clear
72//! **Keybinding**: GetResolved
73//! **LanguageFeatures**: $languageFeatures:registerProvider
74//! **Search**: TextSearch
75//! **SourceControlManagement**: $scm:createSourceControl, updateSourceControl,
76//! updateGroup, registerInputBox **StatusBar**: $statusBar:set, dispose,
77//! $setStatusBarMessage, $disposeStatusBarMessage **Storage**: Get, Set
78//! **Terminal**: $terminal:create, sendText, dispose
79//! **TreeView**: $tree:register
80//! **UserInterface**: ShowMessage, ShowOpenDialog, ShowSaveDialog
81//! **Webview**: $webview:create, $resolveCustomEditor
82//!
83//! ## TODO
84//!
85//! High Priority:
86//! - [ ] Add command parameter schema validation (JSON schema per command)
87//! - [ ] Implement command permission checking (capability-based security)
88//! - [ ] Add command deprecation warnings and migration
89//! - [ ] Cache frequently created effects (reuse boxed closures)
90//! - [ ] Add command timeout configuration (per-command TTL)
91//! - [ ] Implement command rate limiting (DoS protection)
92//! - [ ] Add command metrics collection (latency, success rate)
93//!
94//! Medium Priority:
95//! - [ ] Implement command aliasing (user-defined shortcuts)
96//! - [ ] Add command migration support (rename, deprecate)
97//! - [ ] Add comprehensive command audit logging
98//! - [ ] Support command chaining and composition
99//! - [ ] Implement command undo/redo integration
100//! - [ ] Split CreateEffectForRequest into individual effect modules
101//!
102//! Low Priority:
103//! - [ ] Add request tracing across the entire pipeline
104
105use std::{future::Future, pin::Pin, sync::Arc};
106
107use base64::{Engine as _, engine::general_purpose::STANDARD};
108use CommonLibrary::{
109	Command::CommandExecutor::CommandExecutor,
110	Configuration::{
111		ConfigurationInspector::ConfigurationInspector,
112		ConfigurationProvider::ConfigurationProvider,
113		DTO::ConfigurationTarget::ConfigurationTarget,
114	},
115	CustomEditor::CustomEditorProvider::CustomEditorProvider,
116	Debug::DebugService::DebugService,
117	Diagnostic::DiagnosticManager::DiagnosticManager,
118	Document::DocumentProvider::DocumentProvider,
119	Environment::Requires::Requires,
120	FileSystem::{FileSystemReader::FileSystemReader, FileSystemWriter::FileSystemWriter},
121	Keybinding::KeybindingProvider::KeybindingProvider,
122	LanguageFeature::{
123		DTO::ProviderType::ProviderType,
124		LanguageFeatureProviderRegistry::LanguageFeatureProviderRegistry,
125	},
126	Search::SearchProvider::SearchProvider,
127	SourceControlManagement::SourceControlManagementProvider::SourceControlManagementProvider,
128	StatusBar::{DTO::StatusBarEntryDTO::StatusBarEntryDTO, StatusBarProvider::StatusBarProvider},
129	Storage::StorageProvider::StorageProvider,
130	Terminal::TerminalProvider::TerminalProvider,
131	TreeView::TreeViewProvider::TreeViewProvider,
132	UserInterface::{DTO::MessageSeverity::MessageSeverity, UserInterfaceProvider::UserInterfaceProvider},
133	Webview::WebviewProvider,
134};
135use serde_json::{Value, json};
136use tauri::{AppHandle, Runtime};
137use url::Url;
138
139use crate::{RunTime::ApplicationRunTime::ApplicationRunTime, Track::Effect::MappedEffectType::MappedEffect};
140use crate::dev_log;
141
142/// Maps a string-based method name (command or RPC) to its corresponding effect
143/// constructor, returning a boxed closure ([`MappedEffect`]) that can be
144/// executed by the ApplicationRunTime.
145///
146/// # Arguments
147/// - `ApplicationHandle`: Tauri app handle for accessing state
148/// - `MethodName`: The command/RPC method name to map
149/// - `Parameters`: JSON value containing parameters for the effect
150///
151/// # Returns
152/// `Result<MappedEffect, String>` - either a boxed async closure or an error
153/// if the command is unknown
154pub fn CreateEffectForRequest<R:Runtime>(
155	_ApplicationHandle:&AppHandle<R>,
156	MethodName:&str,
157	Parameters:Value,
158) -> Result<MappedEffect, String> {
159	match MethodName {
160		// Configuration
161		"Configuration.Inspect" => {
162			let effect =
163				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
164					Box::pin(async move {
165						let provider:Arc<dyn ConfigurationInspector> = run_time.Environment.Require();
166						let section = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
167						let result = provider.InspectConfigurationValue(section, Default::default()).await;
168						result.map(|_opt_dto| json!(null)).map_err(|e| e.to_string())
169					})
170				};
171			Ok(Box::new(effect))
172		},
173
174		"Configuration.Update" => {
175			let effect =
176				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
177					Box::pin(async move {
178						let provider:Arc<dyn ConfigurationProvider> = run_time.Environment.Require();
179						let key = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
180						let value = Parameters.get(1).cloned().unwrap_or_default();
181						let target = match Parameters.get(2).and_then(Value::as_u64) {
182							Some(0) => ConfigurationTarget::User,
183							Some(1) => ConfigurationTarget::Workspace,
184							_ => ConfigurationTarget::User,
185						};
186						let result = provider
187							.UpdateConfigurationValue(key, value, target, Default::default(), None)
188							.await;
189						result.map(|_| json!(null)).map_err(|e| e.to_string())
190					})
191				};
192			Ok(Box::new(effect))
193		},
194
195		// Diagnostics
196		"Diagnostic.Set" => {
197			let effect =
198				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
199					Box::pin(async move {
200						let provider:Arc<dyn DiagnosticManager> = run_time.Environment.Require();
201						let owner = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
202						let entries = Parameters.get(1).cloned().unwrap_or_default();
203						provider
204							.SetDiagnostics(owner, entries)
205							.await
206							.map(|_| json!(null))
207							.map_err(|e| e.to_string())
208					})
209				};
210			Ok(Box::new(effect))
211		},
212
213		"Diagnostic.Clear" => {
214			let effect =
215				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
216					Box::pin(async move {
217						let provider:Arc<dyn DiagnosticManager> = run_time.Environment.Require();
218						let owner = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
219						provider
220							.ClearDiagnostics(owner)
221							.await
222							.map(|_| json!(null))
223							.map_err(|e| e.to_string())
224					})
225				};
226			Ok(Box::new(effect))
227		},
228
229		// Language Features
230		"$languageFeatures:registerProvider" => {
231			let effect =
232				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
233					Box::pin(async move {
234						let provider:Arc<dyn LanguageFeatureProviderRegistry> = run_time.Environment.Require();
235						let id = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
236						let selector = Parameters.get(1).cloned().unwrap_or_default();
237						let extension_id = Parameters.get(2).cloned().unwrap_or_default();
238						let options = Parameters.get(3).cloned();
239						provider
240							.RegisterProvider(id, ProviderType::Hover, selector, extension_id, options)
241							.await
242							.map(|handle| json!(handle))
243							.map_err(|e| e.to_string())
244					})
245				};
246			Ok(Box::new(effect))
247		},
248
249		// Documents
250		"Document.Save" => {
251			let effect =
252				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
253					Box::pin(async move {
254						let document_provider:Arc<dyn DocumentProvider> = run_time.Environment.Require();
255						let uri_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
256						let uri = Url::parse(uri_str).unwrap_or_else(|_| Url::parse("file:///tmp/test.txt").unwrap());
257						document_provider
258							.SaveDocument(uri)
259							.await
260							.map(|success| json!(success))
261							.map_err(|e| e.to_string())
262					})
263				};
264			Ok(Box::new(effect))
265		},
266
267		"Document.SaveAs" => {
268			let effect =
269				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
270					Box::pin(async move {
271						let document_provider:Arc<dyn DocumentProvider> = run_time.Environment.Require();
272						let original_uri_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
273						let original_uri = Url::parse(original_uri_str)
274							.unwrap_or_else(|_| Url::parse("file:///tmp/test.txt").unwrap());
275						let target_uri = Parameters
276							.get(1)
277							.and_then(Value::as_str)
278							.map(Url::parse)
279							.transpose()
280							.unwrap_or(None);
281						document_provider
282							.SaveDocumentAs(original_uri, target_uri)
283							.await
284							.map(|uri_option| json!(uri_option))
285							.map_err(|e| e.to_string())
286					})
287				};
288			Ok(Box::new(effect))
289		},
290
291		// FileSystem
292		"FileSystem.ReadFile" => {
293			let effect =
294				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
295					Box::pin(async move {
296						let fs_reader:Arc<dyn FileSystemReader> = run_time.Environment.Require();
297						let path_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
298						let path = std::path::PathBuf::from(path_str);
299						fs_reader
300							.ReadFile(&path)
301							.await
302							.map(|bytes| json!(bytes))
303							.map_err(|e| e.to_string())
304					})
305				};
306			Ok(Box::new(effect))
307		},
308
309		"FileSystem.WriteFile" => {
310			let effect =
311				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
312					Box::pin(async move {
313						let fs_writer:Arc<dyn FileSystemWriter> = run_time.Environment.Require();
314						let path_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
315						let path = std::path::PathBuf::from(path_str);
316						let content = Parameters.get(1).cloned();
317						let content_bytes = match content {
318							Some(Value::Array(arr)) => {
319								arr.into_iter().filter_map(|v| v.as_u64().map(|n| n as u8)).collect()
320							},
321							Some(Value::String(s)) => STANDARD.decode(&s).unwrap_or_default(),
322							_ => vec![],
323						};
324						fs_writer
325							.WriteFile(&path, content_bytes, true, true)
326							.await
327							.map(|_| json!(null))
328							.map_err(|e| e.to_string())
329					})
330				};
331			Ok(Box::new(effect))
332		},
333
334		"FileSystem.ReadDirectory" => {
335			let effect =
336				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
337					Box::pin(async move {
338						let fs_reader:Arc<dyn FileSystemReader> = run_time.Environment.Require();
339						let path_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
340						let path = std::path::PathBuf::from(path_str);
341						fs_reader
342							.ReadDirectory(&path)
343							.await
344							.map(|entries| json!(entries))
345							.map_err(|e| e.to_string())
346					})
347				};
348			Ok(Box::new(effect))
349		},
350
351		"FileSystem.Stat" => {
352			let effect =
353				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
354					Box::pin(async move {
355						let fs_reader:Arc<dyn FileSystemReader> = run_time.Environment.Require();
356						let path_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
357						let path = std::path::PathBuf::from(path_str);
358						fs_reader
359							.StatFile(&path)
360							.await
361							.map(|stat| json!(stat))
362							.map_err(|e| e.to_string())
363					})
364				};
365			Ok(Box::new(effect))
366		},
367
368		"FileSystem.CreateDirectory" => {
369			let effect =
370				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
371					Box::pin(async move {
372						let fs_writer:Arc<dyn FileSystemWriter> = run_time.Environment.Require();
373						let path_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
374						let path = std::path::PathBuf::from(path_str);
375						fs_writer
376							.CreateDirectory(&path, true)
377							.await
378							.map(|_| json!(null))
379							.map_err(|e| e.to_string())
380					})
381				};
382			Ok(Box::new(effect))
383		},
384
385		"FileSystem.Delete" => {
386			let effect =
387				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
388					Box::pin(async move {
389						let fs_writer:Arc<dyn FileSystemWriter> = run_time.Environment.Require();
390						let path_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
391						let path = std::path::PathBuf::from(path_str);
392						let recursive = Parameters.get(1).and_then(Value::as_bool).unwrap_or(false);
393						fs_writer
394							.Delete(&path, recursive, false)
395							.await
396							.map(|_| json!(null))
397							.map_err(|e| e.to_string())
398					})
399				};
400			Ok(Box::new(effect))
401		},
402
403		"FileSystem.Rename" => {
404			let effect =
405				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
406					Box::pin(async move {
407						let fs_writer:Arc<dyn FileSystemWriter> = run_time.Environment.Require();
408						let source = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
409						let target = Parameters.get(1).and_then(Value::as_str).unwrap_or("");
410						fs_writer
411							.Rename(
412								&std::path::PathBuf::from(source),
413								&std::path::PathBuf::from(target),
414								true,
415							)
416							.await
417							.map(|_| json!(null))
418							.map_err(|e| e.to_string())
419					})
420				};
421			Ok(Box::new(effect))
422		},
423
424		"FileSystem.Copy" => {
425			let effect =
426				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
427					Box::pin(async move {
428						let fs_writer:Arc<dyn FileSystemWriter> = run_time.Environment.Require();
429						let source = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
430						let target = Parameters.get(1).and_then(Value::as_str).unwrap_or("");
431						fs_writer
432							.Copy(
433								&std::path::PathBuf::from(source),
434								&std::path::PathBuf::from(target),
435								true,
436							)
437							.await
438							.map(|_| json!(null))
439							.map_err(|e| e.to_string())
440					})
441				};
442			Ok(Box::new(effect))
443		},
444
445		// Keybinding
446		"Keybinding.GetResolved" => {
447			let effect =
448				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
449					Box::pin(async move {
450						let provider:Arc<dyn KeybindingProvider> = run_time.Environment.Require();
451						provider.GetResolvedKeybinding().await.map_err(|e| e.to_string())
452					})
453				};
454			Ok(Box::new(effect))
455		},
456
457		// Search
458		"Search.TextSearch" => {
459			let effect =
460				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
461					Box::pin(async move {
462						let provider:Arc<dyn SearchProvider> = run_time.Environment.Require();
463						let query = Parameters.get(0).cloned().unwrap_or_default();
464						let options = Parameters.get(1).cloned().unwrap_or_default();
465						provider.TextSearch(query, options).await.map_err(|e| e.to_string())
466					})
467				};
468			Ok(Box::new(effect))
469		},
470
471		// Storage
472		"Storage.Get" => {
473			let effect =
474				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
475					Box::pin(async move {
476						let provider:Arc<dyn StorageProvider> = run_time.Environment.Require();
477						let key = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
478						provider
479							.GetStorageValue(false, &key)
480							.await
481							.map(|opt_val| json!(opt_val))
482							.map_err(|e| e.to_string())
483					})
484				};
485			Ok(Box::new(effect))
486		},
487
488		"Storage.Set" => {
489			let effect =
490				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
491					Box::pin(async move {
492						let provider:Arc<dyn StorageProvider> = run_time.Environment.Require();
493						let key = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
494						let value = Parameters.get(1).cloned();
495						provider
496							.UpdateStorageValue(false, key, value)
497							.await
498							.map(|_| json!(null))
499							.map_err(|e| e.to_string())
500					})
501				};
502			Ok(Box::new(effect))
503		},
504
505		// Commands
506		"Command.Execute" => {
507			let effect =
508				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
509					Box::pin(async move {
510						let command_executor:Arc<dyn CommandExecutor> = run_time.Environment.Require();
511						let command_id = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
512						let args = Parameters.get(1).cloned().unwrap_or_default();
513						command_executor
514							.ExecuteCommand(command_id, args)
515							.await
516							.map_err(|e| e.to_string())
517					})
518				};
519			Ok(Box::new(effect))
520		},
521
522		"Command.GetAll" => {
523			let effect =
524				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
525					Box::pin(async move {
526						let provider:Arc<dyn CommandExecutor> = run_time.Environment.Require();
527						provider
528							.GetAllCommands()
529							.await
530							.map(|cmds| json!(cmds))
531							.map_err(|e| e.to_string())
532					})
533				};
534			Ok(Box::new(effect))
535		},
536
537		// Status Bar
538		"$statusBar:set" => {
539			let effect =
540				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
541					Box::pin(async move {
542						let provider:Arc<dyn StatusBarProvider> = run_time.Environment.Require();
543						// Construct a minimal StatusBarEntryDTO from parameters
544						let text = Parameters.get(0).and_then(Value::as_str).unwrap_or("status").to_string();
545						let entry = StatusBarEntryDTO {
546							EntryIdentifier:"id".to_string(),
547							ItemIdentifier:"item".to_string(),
548							ExtensionIdentifier:"ext".to_string(),
549							Name:None,
550							Text:text,
551							Tooltip:None,
552							HasTooltipProvider:false,
553							Command:None,
554							Color:None,
555							BackgroundColor:None,
556							IsAlignedLeft:false,
557							Priority:None,
558							AccessibilityInformation:None,
559						};
560						provider
561							.SetStatusBarEntry(entry)
562							.await
563							.map(|_| json!(null))
564							.map_err(|e| e.to_string())
565					})
566				};
567			Ok(Box::new(effect))
568		},
569
570		"$statusBar:dispose" => {
571			let effect =
572				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
573					Box::pin(async move {
574						let provider:Arc<dyn StatusBarProvider> = run_time.Environment.Require();
575						let id = Parameters.get(0).and_then(Value::as_str).unwrap_or("id").to_string();
576						provider
577							.DisposeStatusBarEntry(id)
578							.await
579							.map(|_| json!(null))
580							.map_err(|e| e.to_string())
581					})
582				};
583			Ok(Box::new(effect))
584		},
585
586		"$setStatusBarMessage" => {
587			let effect =
588				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
589					Box::pin(async move {
590						let provider:Arc<dyn StatusBarProvider> = run_time.Environment.Require();
591						let message_id = Parameters.get(0).and_then(Value::as_str).unwrap_or("msg_id").to_string();
592						let text = Parameters.get(1).and_then(Value::as_str).unwrap_or("message").to_string();
593						provider
594							.SetStatusBarMessage(message_id, text)
595							.await
596							.map(|_| json!(null))
597							.map_err(|e| e.to_string())
598					})
599				};
600			Ok(Box::new(effect))
601		},
602
603		"$disposeStatusBarMessage" => {
604			let effect =
605				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
606					Box::pin(async move {
607						let provider:Arc<dyn StatusBarProvider> = run_time.Environment.Require();
608						let message_id = Parameters.get(0).and_then(Value::as_str).unwrap_or("msg_id").to_string();
609						provider
610							.DisposeStatusBarMessage(message_id)
611							.await
612							.map(|_| json!(null))
613							.map_err(|e| e.to_string())
614					})
615				};
616			Ok(Box::new(effect))
617		},
618
619		// User Interface
620		"UserInterface.ShowMessage" => {
621			let effect =
622				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
623					Box::pin(async move {
624						let provider:Arc<dyn UserInterfaceProvider> = run_time.Environment.Require();
625						let severity_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("info");
626						let message = Parameters.get(1).and_then(Value::as_str).unwrap_or("").to_string();
627						let options = Parameters.get(2).cloned();
628						let severity = match severity_str {
629							"warning" => MessageSeverity::Warning,
630							"error" => MessageSeverity::Error,
631							_ => MessageSeverity::Info,
632						};
633						provider
634							.ShowMessage(severity, message, options)
635							.await
636							.map(|_| json!(null))
637							.map_err(|e| e.to_string())
638					})
639				};
640			Ok(Box::new(effect))
641		},
642
643		"UserInterface.ShowQuickPick" => {
644			let effect =
645				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
646					Box::pin(async move {
647						let provider:Arc<dyn UserInterfaceProvider> = run_time.Environment.Require();
648						// Using default empty parameters for now
649						let (items, options) = (
650							vec![],
651							None as Option<CommonLibrary::UserInterface::DTO::QuickPickOptionsDTO::QuickPickOptionsDTO>,
652						);
653						provider
654							.ShowQuickPick(items, options)
655							.await
656							.map(|selected_items| json!(selected_items))
657							.map_err(|e| e.to_string())
658					})
659				};
660			Ok(Box::new(effect))
661		},
662
663		"UserInterface.ShowInputBox" => {
664			let effect =
665				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
666					Box::pin(async move {
667						let provider:Arc<dyn UserInterfaceProvider> = run_time.Environment.Require();
668						let options = if let Some(Value::Object(obj)) = Parameters.get(0) {
669							// Properly deserialize to InputBoxOptionsDTO
670							match serde_json::from_value::<
671								CommonLibrary::UserInterface::DTO::InputBoxOptionsDTO::InputBoxOptionsDTO,
672							>(Value::Object(obj.clone()))
673							{
674								Ok(dto) => Some(dto),
675								Err(e) => {
676									dev_log!("ipc", "warn: Failed to deserialize InputBoxOptionsDTO: {}", e);
677									Some(CommonLibrary::UserInterface::DTO::InputBoxOptionsDTO::InputBoxOptionsDTO::default())
678								},
679							}
680						} else {
681							None
682						};
683						provider
684							.ShowInputBox(options)
685							.await
686							.map(|input_opt| json!(input_opt))
687							.map_err(|e| e.to_string())
688					})
689				};
690			Ok(Box::new(effect))
691		},
692
693		"UserInterface.ShowOpenDialog" => {
694			let effect =
695				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
696					Box::pin(async move {
697						let provider:Arc<dyn UserInterfaceProvider> = run_time.Environment.Require();
698						let options = if let Some(Value::Object(obj)) = Parameters.get(0) {
699							// Properly deserialize to OpenDialogOptionsDTO
700							match serde_json::from_value::<
701								CommonLibrary::UserInterface::DTO::OpenDialogOptionsDTO::OpenDialogOptionsDTO,
702							>(Value::Object(obj.clone()))
703							{
704								Ok(dto) => Some(dto),
705								Err(e) => {
706									dev_log!("ipc", "warn: Failed to deserialize OpenDialogOptionsDTO: {}", e);
707									Some(Default::default())
708								},
709							}
710						} else {
711							None
712						};
713						provider
714							.ShowOpenDialog(options)
715							.await
716							.map(|path_buf_opt| json!(path_buf_opt))
717							.map_err(|e| e.to_string())
718					})
719				};
720			Ok(Box::new(effect))
721		},
722
723		"UserInterface.ShowSaveDialog" => {
724			let effect =
725				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
726					Box::pin(async move {
727						let provider:Arc<dyn UserInterfaceProvider> = run_time.Environment.Require();
728						let options = if let Some(Value::Object(obj)) = Parameters.get(0) {
729							// Properly deserialize to SaveDialogOptionsDTO
730							match serde_json::from_value::<
731								CommonLibrary::UserInterface::DTO::SaveDialogOptionsDTO::SaveDialogOptionsDTO,
732							>(Value::Object(obj.clone()))
733							{
734								Ok(dto) => Some(dto),
735								Err(e) => {
736									dev_log!("ipc", "warn: Failed to deserialize SaveDialogOptionsDTO: {}", e);
737									Some(Default::default())
738								},
739							}
740						} else {
741							None
742						};
743						provider
744							.ShowSaveDialog(options)
745							.await
746							.map(|path_buf_opt| json!(path_buf_opt))
747							.map_err(|e| e.to_string())
748					})
749				};
750			Ok(Box::new(effect))
751		},
752
753		// Terminal
754		"$terminal:create" => {
755			let effect =
756				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
757					Box::pin(async move {
758						let provider:Arc<dyn TerminalProvider> = run_time.Environment.Require();
759						let options = Parameters.get(0).cloned().unwrap_or_default();
760						provider.CreateTerminal(options).await.map_err(|e| e.to_string())
761					})
762				};
763			Ok(Box::new(effect))
764		},
765
766		"$terminal:sendText" => {
767			let effect =
768				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
769					Box::pin(async move {
770						let provider:Arc<dyn TerminalProvider> = run_time.Environment.Require();
771						let terminal_id = Parameters.get(0).and_then(Value::as_i64).map(|n| n as u64).unwrap_or(0);
772						let text = Parameters.get(1).and_then(Value::as_str).unwrap_or("").to_string();
773						provider
774							.SendTextToTerminal(terminal_id, text)
775							.await
776							.map(|_| json!(null))
777							.map_err(|e| e.to_string())
778					})
779				};
780			Ok(Box::new(effect))
781		},
782
783		"$terminal:dispose" => {
784			let effect =
785				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
786					Box::pin(async move {
787						let provider:Arc<dyn TerminalProvider> = run_time.Environment.Require();
788						let terminal_id = Parameters.get(0).and_then(Value::as_i64).map(|n| n as u64).unwrap_or(0);
789						provider
790							.DisposeTerminal(terminal_id)
791							.await
792							.map(|_| json!(null))
793							.map_err(|e| e.to_string())
794					})
795				};
796			Ok(Box::new(effect))
797		},
798
799		// Webview
800		"$webview:create" => {
801			let effect =
802				move |_run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
803					Box::pin(async move {
804						dev_log!("ipc", "warn: $webview:create not fully implemented");
805						Ok(json!({"handle": "webview-123"}))
806					})
807				};
808			Ok(Box::new(effect))
809		},
810
811		"$resolveCustomEditor" => {
812			let effect =
813				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
814					Box::pin(async move {
815						let provider:Arc<dyn CustomEditorProvider> = run_time.Environment.Require();
816						let view_type = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
817						let resource_uri_str = Parameters.get(1).and_then(Value::as_str).unwrap_or("");
818						let resource_uri = Url::parse(resource_uri_str)
819							.unwrap_or_else(|_| Url::parse("file:///tmp/test.txt").unwrap());
820						let webview_handle =
821							Parameters.get(2).and_then(Value::as_str).unwrap_or("webview-123").to_string();
822						provider
823							.ResolveCustomEditor(view_type, resource_uri, webview_handle)
824							.await
825							.map(|_| json!(null))
826							.map_err(|e| e.to_string())
827					})
828				};
829			Ok(Box::new(effect))
830		},
831
832		// Debug
833		"Debug.Start" => {
834			let effect =
835				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
836					Box::pin(async move {
837						let provider:Arc<dyn DebugService> = run_time.Environment.Require();
838						let folder_uri_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
839						let folder_uri = if folder_uri_str.is_empty() { None } else { Url::parse(folder_uri_str).ok() };
840						let configuration = Parameters.get(1).cloned().unwrap_or_else(|| json!({ "type": "node" }));
841						provider
842							.StartDebugging(folder_uri, configuration)
843							.await
844							.map(|session_id| json!(session_id))
845							.map_err(|e| e.to_string())
846					})
847				};
848			Ok(Box::new(effect))
849		},
850
851		"Debug.RegisterConfigurationProvider" => {
852			let effect =
853				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
854					Box::pin(async move {
855						let provider:Arc<dyn DebugService> = run_time.Environment.Require();
856						let debug_type = Parameters.get(0).and_then(Value::as_str).unwrap_or("node").to_string();
857						let provider_handle = Parameters.get(1).and_then(Value::as_i64).map(|n| n as u32).unwrap_or(1);
858						let sidecar_id = Parameters.get(2).and_then(Value::as_str).unwrap_or("cocoon-main").to_string();
859						provider
860							.RegisterDebugConfigurationProvider(debug_type, provider_handle, sidecar_id)
861							.await
862							.map(|_| json!(null))
863							.map_err(|e| e.to_string())
864					})
865				};
866			Ok(Box::new(effect))
867		},
868
869		// Tree View
870		"$tree:register" => {
871			let effect =
872				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
873					Box::pin(async move {
874						let provider:Arc<dyn TreeViewProvider> = run_time.Environment.Require();
875						let view_id = Parameters.get(0).and_then(Value::as_str).unwrap_or("viewId").to_string();
876						let options = Parameters.get(1).cloned().unwrap_or_default();
877						provider
878							.RegisterTreeDataProvider(view_id, options)
879							.await
880							.map(|_| json!(null))
881							.map_err(|e| e.to_string())
882					})
883				};
884			Ok(Box::new(effect))
885		},
886
887		// Source Control Management
888		"$scm:createSourceControl" => {
889			let effect =
890				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
891					Box::pin(async move {
892						let provider:Arc<dyn SourceControlManagementProvider> = run_time.Environment.Require();
893						let resource = Parameters.get(0).cloned().unwrap_or_default();
894						provider
895							.CreateSourceControl(resource)
896							.await
897							.map(|handle| json!(handle))
898							.map_err(|e| e.to_string())
899					})
900				};
901			Ok(Box::new(effect))
902		},
903
904		"$scm:updateSourceControl" => {
905			let effect =
906				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
907					Box::pin(async move {
908						let provider:Arc<dyn SourceControlManagementProvider> = run_time.Environment.Require();
909						let handle = Parameters.get(0).and_then(Value::as_i64).map(|n| n as u32).unwrap_or(0);
910						let update = Parameters.get(1).cloned().unwrap_or_default();
911						provider
912							.UpdateSourceControl(handle, update)
913							.await
914							.map(|_| json!(null))
915							.map_err(|e| e.to_string())
916					})
917				};
918			Ok(Box::new(effect))
919		},
920
921		"$scm:updateGroup" => {
922			let effect =
923				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
924					Box::pin(async move {
925						let provider:Arc<dyn SourceControlManagementProvider> = run_time.Environment.Require();
926						let handle = Parameters.get(0).and_then(Value::as_i64).map(|n| n as u32).unwrap_or(0);
927						let group_data = Parameters.get(1).cloned().unwrap_or_default();
928						provider
929							.UpdateSourceControlGroup(handle, group_data)
930							.await
931							.map(|_| json!(null))
932							.map_err(|e| e.to_string())
933					})
934				};
935			Ok(Box::new(effect))
936		},
937
938		"$scm:registerInputBox" => {
939			let effect =
940				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
941					Box::pin(async move {
942						let provider:Arc<dyn SourceControlManagementProvider> = run_time.Environment.Require();
943						let handle = Parameters.get(0).and_then(Value::as_i64).map(|n| n as u32).unwrap_or(0);
944						let options = Parameters.get(1).cloned().unwrap_or_default();
945						provider
946							.RegisterInputBox(handle, options)
947							.await
948							.map(|_| json!(null))
949							.map_err(|e| e.to_string())
950					})
951				};
952			Ok(Box::new(effect))
953		},
954
955		// Debug — Stop (Cascade-8 stub; DebugService has no StopDebugging yet,
956		// extensions receive `null` instead of "Unknown method")
957		"Debug.Stop" => {
958			let effect =
959				move |_run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
960					Box::pin(async move {
961						let session_id = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
962						dev_log!("ipc", "[Debug.Stop] stub — session={} (TODO: DebugService::StopDebugging)", session_id);
963						Ok(json!(null))
964					})
965				};
966			Ok(Box::new(effect))
967		},
968
969		// Task — Fetch/Execute (Cascade-8 stubs; no TaskProvider trait in
970		// Common yet. Returning safe defaults keeps extensions from
971		// crashing on `vscode.tasks.fetchTasks()` / `executeTask`.)
972		"Task.Fetch" => {
973			let effect =
974				move |_run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
975					Box::pin(async move {
976						dev_log!("ipc", "[Task.Fetch] stub — returning [] (TODO: TaskProvider trait)");
977						Ok(json!([]))
978					})
979				};
980			Ok(Box::new(effect))
981		},
982
983		"Task.Execute" => {
984			let effect =
985				move |_run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
986					Box::pin(async move {
987						dev_log!("ipc", "[Task.Execute] stub — returning null (TODO: TaskProvider trait)");
988						Ok(json!(null))
989					})
990				};
991			Ok(Box::new(effect))
992		},
993
994		// Authentication — GetSession/GetAccounts (Cascade-8 stubs; no
995		// AuthenticationProvider trait yet. Returning `null` / `[]` lets
996		// GitHub/Copilot extensions proceed in "unauthenticated" mode
997		// instead of crashing.)
998		"Authentication.GetSession" => {
999			let effect =
1000				move |_run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
1001					Box::pin(async move {
1002						let provider_id =
1003							Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
1004						dev_log!("ipc", "[Authentication.GetSession] stub — provider={} (TODO: AuthenticationProvider trait)", provider_id);
1005						Ok(json!(null))
1006					})
1007				};
1008			Ok(Box::new(effect))
1009		},
1010
1011		"Authentication.GetAccounts" => {
1012			let effect =
1013				move |_run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
1014					Box::pin(async move {
1015						let provider_id =
1016							Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
1017						dev_log!("ipc", "[Authentication.GetAccounts] stub — provider={} (TODO: AuthenticationProvider trait)", provider_id);
1018						Ok(json!([]))
1019					})
1020				};
1021			Ok(Box::new(effect))
1022		},
1023
1024		// Clipboard — Read/Write (Cascade-8 stubs; tauri-plugin-clipboard-manager
1025		// not yet on the Mountain crate. Read returns "", Write accepts and drops.)
1026		"Clipboard.Read" => {
1027			let effect =
1028				move |_run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
1029					Box::pin(async move {
1030						dev_log!("ipc", "[Clipboard.Read] stub — returning '' (TODO: tauri-plugin-clipboard-manager)");
1031						Ok(json!(""))
1032					})
1033				};
1034			Ok(Box::new(effect))
1035		},
1036
1037		"Clipboard.Write" => {
1038			let effect =
1039				move |_run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
1040					Box::pin(async move {
1041						let text_len = Parameters.get(0).and_then(Value::as_str).map(str::len).unwrap_or(0);
1042						dev_log!("ipc", "[Clipboard.Write] stub — text_len={} (TODO: tauri-plugin-clipboard-manager)", text_len);
1043						Ok(json!(null))
1044					})
1045				};
1046			Ok(Box::new(effect))
1047		},
1048
1049		// NativeHost — OpenExternal (Cascade-8 stub; tauri-plugin-shell not
1050		// on the Mountain crate yet. Logs the URL; returns success so the
1051		// extension's promise resolves.)
1052		"NativeHost.OpenExternal" => {
1053			let effect =
1054				move |_run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
1055					Box::pin(async move {
1056						let uri = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
1057						dev_log!("ipc", "[NativeHost.OpenExternal] stub — uri={} (TODO: tauri-plugin-shell)", uri);
1058						Ok(json!(true))
1059					})
1060				};
1061			Ok(Box::new(effect))
1062		},
1063
1064		// Languages — GetAll (Cascade-8 stub; enumerates nothing yet. Real
1065		// implementation would walk ApplicationState.Extension.ScannedExtensions
1066		// collecting `contributes.languages[]`.)
1067		"Languages.GetAll" => {
1068			let effect =
1069				move |_run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
1070					Box::pin(async move {
1071						dev_log!("ipc", "[Languages.GetAll] stub — returning [] (TODO: enumerate ScannedExtensions)");
1072						Ok(json!([]))
1073					})
1074				};
1075			Ok(Box::new(effect))
1076		},
1077
1078		// Unknown command
1079		_ => {
1080			dev_log!("ipc", "warn: [EffectCreation] Unknown method: {}", MethodName);
1081			Err(format!("Unknown method: {}", MethodName))
1082		},
1083	}
1084}