//! Universal Outcome Transcoder. //! //! Compiles session outcomes, dynamic living canvases (tables, dataframes, //! decision matrices), and cited claims into standalone, zero-dependency, //! responsive HTML documents suitable for distribution, archiving, or viewing. use serde_json::Value; /// Escape HTML special characters to prevent injection. pub fn html_escape(input: &str) -> String { let mut escaped = String::with_capacity(input.len()); for c in input.chars() { match c { '&' => escaped.push_str("&"), '<' => escaped.push_str("<"), '>' => escaped.push_str(">"), '"' => escaped.push_str("""), '\'' => escaped.push_str("'"), other => escaped.push(other), } } escaped } /// Transcodes an outcome markdown transcript with structured vak-blocks /// into a self-contained interactive HTML document. pub fn transcode_to_html(title: &str, content: &str) -> String { let safe_title = html_escape(title); let body_html = transcode_body(content); format!( r#" {safe_title}

{safe_title}

{body_html}
"# ) } fn transcode_body(content: &str) -> String { let mut out = String::new(); let mut lines = content.lines().peekable(); while let Some(line) = lines.next() { let trimmed = line.trim(); // 1. Check for structured vak code blocks if trimmed.starts_with("```vak-table") || trimmed.starts_with("```vak-dataframe") { let mut json_str = String::new(); for next in lines.by_ref() { if next.trim() == "```" { break; } json_str.push_str(next); json_str.push('\n'); } if let Ok(v) = serde_json::from_str::(&json_str) { out.push_str(&render_vak_table(&v)); } continue; } if trimmed.starts_with("```vak-decision") || trimmed.starts_with("```vak-comparison") { let mut json_str = String::new(); for next in lines.by_ref() { if next.trim() == "```" { break; } json_str.push_str(next); json_str.push('\n'); } if let Ok(v) = serde_json::from_str::(&json_str) { out.push_str(&render_vak_decision(&v)); } continue; } // Standard code blocks if trimmed.starts_with("```") { let lang = trimmed.trim_start_matches('`').trim(); let mut code_str = String::new(); for next in lines.by_ref() { if next.trim() == "```" { break; } code_str.push_str(next); code_str.push('\n'); } out.push_str(&format!( "
{}
\n", html_escape(lang), html_escape(&code_str) )); continue; } // Headings if let Some(rest) = trimmed.strip_prefix("### ") { out.push_str(&format!("

{}

\n", render_inline(rest))); continue; } if let Some(rest) = trimmed.strip_prefix("## ") { out.push_str(&format!("

{}

\n", render_inline(rest))); continue; } if let Some(rest) = trimmed.strip_prefix("# ") { out.push_str(&format!("

{}

\n", render_inline(rest))); continue; } // Blockquotes if let Some(rest) = trimmed.strip_prefix("> ") { out.push_str(&format!( "
{}
\n", render_inline(rest) )); continue; } // Unordered Lists if let Some(rest) = trimmed .strip_prefix("- ") .or_else(|| trimmed.strip_prefix("* ")) { out.push_str(&format!("\n", render_inline(rest))); continue; } // Empty lines if trimmed.is_empty() { continue; } // Regular paragraphs out.push_str(&format!("

{}

\n", render_inline(trimmed))); } out } fn render_inline(text: &str) -> String { let out = html_escape(text); // Transform footnote citations [^1] into [^1] let mut res = String::new(); let mut rest = out.as_str(); while let Some(start) = rest.find("[^") { res.push_str(&rest[..start]); let after = &rest[start..]; if let Some(end) = after.find(']') { let tag = &after[..end + 1]; res.push_str(&format!( "{}", html_escape(tag), html_escape(tag) )); rest = &after[end + 1..]; } else { res.push_str(after); rest = ""; break; } } res.push_str(rest); res } fn render_vak_table(v: &Value) -> String { let title = v .get("title") .and_then(Value::as_str) .unwrap_or("Data Table"); let cols = v.get("columns").and_then(Value::as_array); let rows = v.get("rows").and_then(Value::as_array); let mut out = format!( "
\n
\n {}\n \n
\n
\n \n \n", html_escape(title) ); if let Some(cols) = cols { for c in cols { let col_name = c.as_str().unwrap_or(""); out.push_str(&format!( " \n", html_escape(col_name) )); } } out.push_str(" \n \n"); if let Some(rows) = rows { for row in rows { out.push_str(" \n"); if let Some(cells) = row.as_array() { for cell in cells { let text = match cell { Value::String(s) => s.clone(), other => other.to_string(), }; out.push_str(&format!(" \n", html_escape(&text))); } } out.push_str(" \n"); } } out.push_str(" \n
{} ▲▼
{}
\n
\n
\n"); out } fn render_vak_decision(v: &Value) -> String { let mut out = String::from("
\n"); if let Some(options) = v.get("options").and_then(Value::as_array) { for opt in options { let label = opt .get("label") .or_else(|| opt.get("name")) .and_then(Value::as_str) .unwrap_or("Option"); let summary = opt .get("summary") .or_else(|| opt.get("description")) .and_then(Value::as_str) .unwrap_or(""); out.push_str(&format!( "
\n
{}
\n
{}
\n", html_escape(label), html_escape(summary) )); if let Some(pros) = opt.get("pros").and_then(Value::as_array) { out.push_str("
    \n"); for p in pros { out.push_str(&format!( "
  • + {}
  • \n", html_escape(p.as_str().unwrap_or("")) )); } out.push_str("
\n"); } if let Some(cons) = opt.get("cons").and_then(Value::as_array) { out.push_str("
    \n"); for c in cons { out.push_str(&format!( "
  • - {}
  • \n", html_escape(c.as_str().unwrap_or("")) )); } out.push_str("
\n"); } out.push_str("
\n"); } } out.push_str("
\n"); out } #[cfg(test)] mod tests { use super::*; #[test] fn transcoder_renders_standalone_html_with_table_and_decision() { let text = r#" # Executive Analysis A comprehensive evaluation[^1] of architecture choices. ```vak-table { "title": "System Latencies", "columns": ["Service", "p50 (ms)", "p99 (ms)"], "rows": [ ["Auth Gateway", "12", "45"], ["Payment Broker", "34", "120"] ] } ``` ## Architectural Tradeoffs ```vak-decision { "options": [ { "label": "Option A: Event-Driven", "summary": "Decoupled async message bus", "pros": ["High scalability", "Fault tolerant"], "cons": ["Eventual consistency"] }, { "label": "Option B: Sync REST", "summary": "Point-to-point HTTP/2", "pros": ["Immediate consistency"], "cons": ["Cascading latency"] } ] } ``` [^1]: Verified via 2026 Telemetry Audit "#; let html = transcode_to_html("System Architecture Brief", text); assert!(html.contains("")); assert!(html.contains("System Architecture Brief")); assert!(html.contains("System Latencies")); assert!(html.contains("Auth Gateway")); assert!(html.contains("Option A: Event-Driven")); assert!(html.contains("Option B: Sync REST")); assert!(html.contains("citation-sup")); assert!(html.contains("document.querySelectorAll('.card-table-container')")); } }