lean_ctx/core/
wrapped_share.rs1use crate::core::wrapped::{format_tokens, WrappedReport};
14
15impl WrappedReport {
16 pub fn to_share_html(&self, base_url: Option<&str>) -> String {
19 let title = "lean-ctx Wrapped";
20 let period_label = match self.period.as_str() {
21 "week" => "this week",
22 "month" => "this month",
23 _ => "with lean-ctx",
24 };
25 let desc = format!(
26 "I saved {} tokens (~${:.2}) {period_label}. Your AI saw only what mattered.",
27 format_tokens(self.tokens_saved),
28 self.cost_avoided_usd,
29 );
30 let svg = self.to_svg();
31 let meta = social_meta(title, &desc, base_url);
32
33 format!(
34 r#"<!DOCTYPE html>
35<html lang="en">
36<head>
37 <meta charset="utf-8"/>
38 <meta name="viewport" content="width=device-width, initial-scale=1"/>
39 <title>{title}</title>
40 <meta name="description" content="{desc_attr}"/>
41 <style>body{{margin:0;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:22px;background:#0b1020;font-family:Inter,system-ui,-apple-system,Segoe UI,Roboto,sans-serif}}.card{{width:min(1200px,94vw)}}.card svg{{width:100%;height:auto;display:block;border-radius:12px}}a.cta{{color:#34d399;text-decoration:none;font-size:18px;font-weight:600}}</style>
42{meta}</head>
43<body>
44 <div class="card">
45{svg}
46 </div>
47 <a class="cta" href="https://leanctx.com">Get lean-ctx — your AI saw only what mattered →</a>
48</body>
49</html>
50"#,
51 desc_attr = escape(&desc),
52 )
53 }
54
55 pub fn share_text(&self, url: Option<&str>) -> String {
58 let period_label = match self.period.as_str() {
59 "week" => " this week",
60 "month" => " this month",
61 _ => "",
62 };
63 let est = if self.pricing_estimated {
64 " (est.)"
65 } else {
66 ""
67 };
68 let mut s = format!(
69 "I saved {} tokens (~${:.2}{est}){period_label} with lean-ctx — my AI saw only what mattered.",
70 format_tokens(self.tokens_saved),
71 self.cost_avoided_usd,
72 );
73 if let Some(u) = url {
74 s.push(' ');
75 s.push_str(u);
76 }
77 s
78 }
79}
80
81fn social_meta(title: &str, desc: &str, base_url: Option<&str>) -> String {
83 let mut m = String::new();
84 m.push_str(&tag_prop("og:title", title));
85 m.push_str(&tag_prop("og:description", desc));
86 m.push_str(" <meta property=\"og:type\" content=\"website\"/>\n");
87 m.push_str(&tag_name("twitter:title", title));
88 m.push_str(&tag_name("twitter:description", desc));
89
90 if let Some(base) = base_url {
91 let base = base.trim_end_matches('/');
92 let image = format!("{base}/lean-ctx-wrapped.png");
93 m.push_str(&tag_prop("og:url", base));
94 m.push_str(&tag_prop("og:image", &image));
95 m.push_str(" <meta name=\"twitter:card\" content=\"summary_large_image\"/>\n");
96 m.push_str(&tag_name("twitter:image", &image));
97 } else {
98 m.push_str(" <meta name=\"twitter:card\" content=\"summary\"/>\n");
99 }
100 m
101}
102
103fn tag_prop(property: &str, content: &str) -> String {
104 format!(
105 " <meta property=\"{property}\" content=\"{}\"/>\n",
106 escape(content)
107 )
108}
109
110fn tag_name(name: &str, content: &str) -> String {
111 format!(
112 " <meta name=\"{name}\" content=\"{}\"/>\n",
113 escape(content)
114 )
115}
116
117fn escape(s: &str) -> String {
119 s.replace('&', "&")
120 .replace('<', "<")
121 .replace('>', ">")
122 .replace('"', """)
123 .replace('\'', "'")
124}
125
126#[cfg(test)]
127mod tests {
128 use crate::core::wrapped::WrappedReport;
129
130 fn sample() -> WrappedReport {
131 WrappedReport {
132 period: "all".into(),
133 tokens_saved: 348_300_000,
134 tokens_input: 580_000_000,
135 cost_avoided_usd: 870.81,
136 total_commands: 17_055,
137 sessions_count: 67,
138 top_commands: vec![("ctx_search".into(), 100, 60.0)],
139 compression_rate_pct: 60.2,
140 files_touched: 1_234,
141 daily_savings: vec![10, 50, 30, 80, 20, 40, 60],
142 bounce_tokens: 0,
143 model_key: "claude-3.5-sonnet".into(),
144 pricing_estimated: false,
145 percentile: Some(99),
146 }
147 }
148
149 #[test]
150 fn page_is_self_contained_and_branded() {
151 let html = sample().to_share_html(None);
152 assert!(html.starts_with("<!DOCTYPE html>"));
153 assert!(html.contains("<svg"), "SVG must be embedded inline");
154 assert!(html.contains("</html>"));
155 assert!(
156 html.contains("leanctx.com"),
157 "viral CTA must link the brand"
158 );
159 assert!(html.contains("348.3M"), "must show the headline metric");
160 }
161
162 #[test]
163 fn without_base_url_no_image_meta() {
164 let html = sample().to_share_html(None);
165 assert!(
166 !html.contains("og:image"),
167 "must not fabricate an image URL without a base"
168 );
169 assert!(html.contains("name=\"twitter:card\" content=\"summary\""));
170 }
171
172 #[test]
173 fn with_base_url_emits_absolute_image_meta() {
174 let html = sample().to_share_html(Some("https://me.dev/wrapped/"));
175 assert!(html.contains("og:image\" content=\"https://me.dev/wrapped/lean-ctx-wrapped.png\""));
176 assert!(html.contains("summary_large_image"));
177 assert!(!html.contains("wrapped//lean-ctx-wrapped.png"));
179 }
180
181 #[test]
182 fn base_url_is_attribute_escaped() {
183 let html = sample().to_share_html(Some("https://me.dev/w?a=1&b=2"));
184 assert!(
185 html.contains("a=1&b=2"),
186 "ampersands in the base URL must be escaped in attributes"
187 );
188 assert!(
189 !html.contains("a=1&b=2\""),
190 "a raw unescaped ampersand must not survive into an attribute"
191 );
192 }
193
194 #[test]
195 fn share_text_is_postable_and_honest() {
196 let txt = sample().share_text(None);
197 assert!(
198 txt.contains("348.3M"),
199 "headline metric must be in the share line"
200 );
201 assert!(txt.contains("lean-ctx"), "must name the brand");
202 assert!(!txt.contains("http"), "no URL when none is supplied");
203 }
204
205 #[test]
206 fn share_text_appends_permalink_and_estimate_marker() {
207 let mut r = sample();
208 r.pricing_estimated = true;
209 let txt = r.share_text(Some("https://leanctx.com/w/abc123"));
210 assert!(txt.ends_with("https://leanctx.com/w/abc123"));
211 assert!(
212 txt.contains("(est.)"),
213 "estimated pricing must be disclosed"
214 );
215 }
216}