diff --git a/crates/typedialog-core/src/nickel/roundtrip.rs b/crates/typedialog-core/src/nickel/roundtrip.rs index 4538f86..15412b0 100644 --- a/crates/typedialog-core/src/nickel/roundtrip.rs +++ b/crates/typedialog-core/src/nickel/roundtrip.rs @@ -325,7 +325,10 @@ impl RoundtripConfig { })?; // Parse TOML form definition - let form = form_parser::parse_toml(&form_content)?; + let mut form = form_parser::parse_toml(&form_content)?; + + // Migrate to unified elements format if needed + form.migrate_to_elements(); // Extract base directory for resolving relative paths (includes, fragments) let base_dir = form_path.parent().unwrap_or_else(|| Path::new(".")); diff --git a/crates/typedialog-web/src/main.rs b/crates/typedialog-web/src/main.rs index 2880366..6f69278 100644 --- a/crates/typedialog-web/src/main.rs +++ b/crates/typedialog-web/src/main.rs @@ -286,6 +286,58 @@ fn detect_output_format(output_path: Option<&PathBuf>, default_format: &str) -> .to_string() } +/// Load default values from a Nickel (.ncl) file +/// +/// Exports the .ncl file to JSON, then extracts and flattens values +/// matching the form fields. +/// +/// Returns a HashMap of field names to their default values. +fn load_nickel_defaults( + ncl_path: &Path, + form_elements: &[form_parser::FormElement], + verbose: bool, +) -> Result> { + use typedialog_core::nickel::NickelCli; + + if verbose { + eprintln!("[web] Loading default values from {}", ncl_path.display()); + } + + NickelCli::verify()?; + let value = NickelCli::export(ncl_path)?; + + match value { + serde_json::Value::Object(map) => { + // Extract fields from elements (which includes fragments after expand_includes) + let form_fields: Vec = form_elements + .iter() + .filter_map(|elem| match elem { + form_parser::FormElement::Field(f) => Some(f.clone()), + _ => None, + }) + .collect(); + + // Extract Nickel structure AND flatten everything (combine both approaches) + let mut combined = extract_nickel_defaults(&map, &form_fields); + let flattened = flatten_json_object(&map); + + // Flattened values fill in gaps not covered by extraction + for (k, v) in flattened { + combined.entry(k).or_insert(v); + } + + if verbose { + eprintln!("[web] Loaded {} default field values", combined.len()); + } + + Ok(combined) + } + _ => Err(Error::validation_failed( + "Defaults .ncl must export to a JSON object".to_string(), + )), + } +} + async fn execute_form( config: PathBuf, defaults: Option, @@ -314,44 +366,14 @@ async fn execute_form( let initial_values = if let Some(defaults_path_input) = defaults { // Resolve defaults path with cascading search let defaults_path = resolve_file_path(&defaults_path_input, base_dir, false); - use typedialog_core::nickel::NickelCli; let extension = defaults_path.extension().and_then(|s| s.to_str()); let is_ncl = extension == Some("ncl"); let is_toml = extension == Some("toml"); let defaults_json: std::collections::HashMap = if is_ncl { - // Convert .ncl to JSON using nickel export - NickelCli::verify()?; - let value = NickelCli::export(&defaults_path)?; - match value { - serde_json::Value::Object(map) => { - // Extract fields from elements (which includes fragments after expand_includes) - let form_fields: Vec = form - .elements - .iter() - .filter_map(|elem| match elem { - form_parser::FormElement::Field(f) => Some(f.clone()), - _ => None, - }) - .collect(); - - // Extract Nickel structure AND flatten everything (combine both approaches) - let mut combined = extract_nickel_defaults(&map, &form_fields); - let flattened = flatten_json_object(&map); - - // Flattened values fill in gaps not covered by extraction - for (k, v) in flattened { - combined.entry(k).or_insert(v); - } - combined - } - _ => { - return Err(Error::validation_failed( - "Defaults .ncl must export to a JSON object".to_string(), - )) - } - } + // Use helper function to load .ncl defaults + load_nickel_defaults(&defaults_path, &form.elements, false)? } else if is_toml { // Read TOML and convert to JSON let defaults_content = fs::read_to_string(&defaults_path).map_err(|e| { @@ -552,7 +574,14 @@ async fn nickel_roundtrip_cmd( form = form_parser::expand_includes(form, form_base_dir) .map_err(|e| Error::validation_failed(format!("Failed to expand includes: {}", e)))?; - // Step 3: Execute form with Web backend (interactive HTTP server) + // Step 3: Load default values from input .ncl if exists + let initial_values = if input.exists() { + Some(load_nickel_defaults(&input, &form.elements, verbose)?) + } else { + None + }; + + // Step 4: Execute form with Web backend (same path as normal execute_form) if verbose { eprintln!("[roundtrip] Starting HTTP server for interactive form"); } @@ -563,9 +592,16 @@ async fn nickel_roundtrip_cmd( println!("Starting interactive form on http://localhost:{}", port); println!("Complete the form and submit to continue...\n"); - let form_results = - form_parser::execute_with_backend_two_phase(form, backend.as_mut(), None, form_base_dir) - .await?; + // USE THE SAME EXECUTION PATH AS NORMAL WEB BACKEND + // This respects display_mode and calls execute_form_complete() when display_mode = "complete" + let form_results = form_parser::execute_with_backend_i18n_with_defaults( + form, + backend.as_mut(), + None, + form_base_dir, + initial_values, + ) + .await?; if verbose { eprintln!(