1use std::collections::BTreeMap;
5
6use serde_json::Value;
7
8pub const MAX_STARS_PER_PROVIDER: usize = 2;
10pub const MAX_BARS: usize = 4;
12pub const BARS_POINT_SIDE: u32 = 18;
14pub const BARS_PIXEL_SIDE: u32 = BARS_POINT_SIDE * 2;
17#[cfg_attr(not(test), allow(dead_code))]
20pub const BARS_SCALES: [u32; 3] = [1, 2, 3];
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum StripStyle {
25 Text,
26 Bars,
27}
28
29impl StripStyle {
30 pub fn parse(name: &str) -> Self {
31 if name.eq_ignore_ascii_case("text") {
32 Self::Text
33 } else {
34 Self::Bars
35 }
36 }
37
38 pub fn as_str(self) -> &'static str {
39 match self {
40 Self::Text => "text",
41 Self::Bars => "bars",
42 }
43 }
44}
45
46pub type Stars = BTreeMap<String, Vec<String>>;
48
49pub type HiddenRows = BTreeMap<String, Vec<String>>;
51
52#[derive(Debug, Clone, PartialEq)]
54pub struct StripMetric {
55 pub provider_id: String,
56 pub provider_name: String,
57 pub key: String,
58 pub label: String,
59 pub value: String,
60 pub fraction: f64,
62 pub bounded: bool,
63 pub left_value: Option<String>,
67 pub left_fraction: Option<f64>,
73 pub value_headline: bool,
77 pub grouped: bool,
80}
81
82pub type StripGroup = (String, String, Vec<StripMetric>);
84
85#[derive(Debug, Clone, PartialEq)]
87pub struct StripContent {
88 pub groups: Vec<StripGroup>,
89 pub bars: Vec<StripMetric>,
90}
91
92impl StripContent {
93 pub fn is_empty(&self) -> bool {
94 self.groups.is_empty()
95 }
96
97 pub fn title_line(&self) -> String {
100 let mut parts = Vec::new();
101 for (_, name, metrics) in &self.groups {
102 let values: Vec<&str> = metrics.iter().map(|m| m.value.as_str()).collect();
103 if values.is_empty() {
104 continue;
105 }
106 parts.push(format!("{name} {}", values.join(" ")));
107 }
108 parts.join(" ")
109 }
110}
111
112#[derive(Debug, Clone, Copy, PartialEq)]
115pub struct BarFill {
116 pub fill_w: f64,
117 pub remainder_w: f64,
118 pub divider_x: Option<f64>,
119}
120
121pub fn visual_fraction(fraction: f64) -> f64 {
124 if !fraction.is_finite() {
125 return 0.0;
126 }
127 let clamped = fraction.clamp(0.0, 1.0);
128 if clamped > 0.7 && clamped < 1.0 {
129 let remainder = 1.0 - clamped;
130 let quantized = ((remainder / 0.15).ceil() * 0.15).min(1.0);
131 (1.0 - quantized).max(0.0)
132 } else {
133 clamped
134 }
135}
136
137pub fn bar_fill(track_w: f64, fraction: f64) -> BarFill {
138 if !fraction.is_finite() || fraction <= 0.0 {
139 return BarFill {
140 fill_w: 0.0,
141 remainder_w: 0.0,
142 divider_x: None,
143 };
144 }
145 let visual = visual_fraction(fraction);
146 if visual >= 1.0 {
147 return BarFill {
148 fill_w: track_w,
149 remainder_w: 0.0,
150 divider_x: None,
151 };
152 }
153 let min_visible = (track_w * 0.2).round().max(4.0);
154 let max_fill_w = (track_w - min_visible).max(1.0);
155 let fill_w = (track_w * visual).round().clamp(1.0, max_fill_w);
156 let true_remainder = track_w - fill_w;
157 let remainder_w = true_remainder.max(min_visible).min(track_w - 1.0);
158 BarFill {
159 fill_w,
160 remainder_w,
161 divider_x: Some(track_w - remainder_w),
162 }
163}
164
165pub fn parse_strip_ipc(value: &Value) -> (StripStyle, Stars, Vec<String>) {
168 let style = StripStyle::Bars;
169 let mut stars = Stars::new();
170 if let Some(map) = value.get("stars").and_then(Value::as_object) {
171 for (id, keys) in map {
172 let id = id.trim();
173 if id.is_empty() {
174 continue;
175 }
176 let mut list = Vec::new();
177 if let Some(arr) = keys.as_array() {
178 for key in arr {
179 let Some(key) = key.as_str() else { continue };
180 let key = key.trim();
181 if key.is_empty() || list.iter().any(|k| k == key) {
182 continue;
183 }
184 list.push(key.to_string());
185 if list.len() == MAX_STARS_PER_PROVIDER {
186 break;
187 }
188 }
189 }
190 if !list.is_empty() {
191 stars.insert(id.to_string(), list);
192 }
193 }
194 }
195 let mut order = Vec::new();
196 if let Some(arr) = value.get("order").and_then(Value::as_array) {
197 for id in arr {
198 let Some(id) = id.as_str() else { continue };
199 let id = id.trim();
200 if id.is_empty() || order.iter().any(|existing| existing == id) {
201 continue;
202 }
203 order.push(id.to_string());
204 }
205 }
206 (style, stars, order)
207}
208
209#[cfg_attr(not(any(target_os = "macos", test)), allow(dead_code))]
213pub fn parse_hidden_rows(value: &Value) -> HiddenRows {
214 let mut hidden = HiddenRows::new();
215 let Some(map) = value.get("hidden_rows").and_then(Value::as_object) else {
216 return hidden;
217 };
218 for (id, keys) in map {
219 let id = id.trim();
220 let Some(keys) = keys.as_array() else {
221 continue;
222 };
223 let mut list: Vec<String> = Vec::new();
224 for key in keys.iter().filter_map(Value::as_str).map(str::trim) {
225 if !key.is_empty() && !list.iter().any(|k| k == key) {
226 list.push(key.to_string());
227 }
228 }
229 if !id.is_empty() && !list.is_empty() {
230 hidden.insert(id.to_string(), list);
231 }
232 }
233 hidden
234}
235
236pub fn content_from_payload(payload: &Value, stars: &Stars, order: &[String]) -> StripContent {
241 let Some(entries) = payload.get("entries").and_then(Value::as_array) else {
242 return StripContent {
243 groups: Vec::new(),
244 bars: Vec::new(),
245 };
246 };
247 let mut by_id = std::collections::HashMap::new();
248 let mut payload_ids = Vec::new();
249 for entry in entries {
250 let Some(id) = entry.get("id").and_then(Value::as_str) else {
251 continue;
252 };
253 payload_ids.push(id.to_string());
254 by_id.insert(id.to_string(), entry);
255 }
256 let mut walk = Vec::new();
257 let mut seen = std::collections::HashSet::new();
258 for id in order {
259 if by_id.contains_key(id) && seen.insert(id.clone()) {
260 walk.push(id.clone());
261 }
262 }
263 for id in payload_ids {
264 if seen.insert(id.clone()) {
265 walk.push(id);
266 }
267 }
268 let groups: Vec<StripGroup> = walk
269 .into_iter()
270 .filter_map(|id| {
271 let entry = by_id.get(&id)?;
272 let starred =
273 (!stars.is_empty()).then(|| stars.get(&id).map(Vec::as_slice).unwrap_or_default());
274 entry_group(&id, entry, starred)
275 })
276 .collect();
277 let bars: Vec<StripMetric> = groups
278 .iter()
279 .flat_map(|(_, _, metrics)| metrics.iter().cloned())
280 .filter(|m| m.bounded)
281 .take(MAX_BARS)
282 .collect();
283 StripContent { groups, bars }
284}
285
286#[cfg_attr(not(any(target_os = "macos", test)), allow(dead_code))]
296pub fn quota_group(payload: &Value, id: &str, hidden: &[String]) -> Option<StripGroup> {
297 let entry = payload
298 .get("entries")?
299 .as_array()?
300 .iter()
301 .find(|entry| entry.get("id").and_then(Value::as_str) == Some(id))?;
302 if entry.get("status").and_then(Value::as_str) == Some("error") {
303 return None;
304 }
305 let name = entry_name(entry, id);
306 let shown: Vec<StripMetric> = metrics_for_entry(entry, id, &name)
307 .into_iter()
308 .filter(|m| !m.value.trim().is_empty() && !hidden.contains(&m.key))
309 .collect();
310 let windows: Vec<StripMetric> = shown.iter().filter(|m| !m.grouped).cloned().collect();
311 let metrics = if windows.is_empty() { shown } else { windows };
312 (!metrics.is_empty()).then(|| (id.to_string(), name, metrics))
313}
314
315fn entry_name(entry: &Value, id: &str) -> String {
316 entry
317 .get("display_name")
318 .or_else(|| entry.get("name"))
319 .and_then(Value::as_str)
320 .unwrap_or(id)
321 .to_string()
322}
323
324fn entry_group(id: &str, entry: &Value, starred: Option<&[String]>) -> Option<StripGroup> {
328 if entry.get("status").and_then(Value::as_str) == Some("error") {
329 return None;
330 }
331 let name = entry_name(entry, id);
332 let metrics = metrics_for_entry(entry, id, &name);
333 let wanted: Vec<String> = match starred {
334 Some(keys) => keys.to_vec(),
335 None => metrics
336 .iter()
337 .filter(|m| m.bounded)
338 .take(MAX_STARS_PER_PROVIDER)
339 .map(|m| m.key.clone())
340 .collect(),
341 };
342 let picked: Vec<StripMetric> = wanted
343 .iter()
344 .filter_map(|key| {
345 metrics
346 .iter()
347 .find(|m| &m.key == key && !m.value.trim().is_empty())
348 .cloned()
349 })
350 .collect();
351 (!picked.is_empty()).then(|| (id.to_string(), name, picked))
352}
353
354fn metrics_for_entry(entry: &Value, id: &str, name: &str) -> Vec<StripMetric> {
355 let Some(sections) = entry.get("sections").and_then(Value::as_array) else {
356 return Vec::new();
357 };
358 let mut group = String::new();
359 let mut rows = Vec::new();
360 let mut seen: BTreeMap<String, u32> = BTreeMap::new();
361 for section in sections {
362 let kind = section.get("type").and_then(Value::as_str).unwrap_or("");
363 if kind == "text" {
364 let label = section.get("label").and_then(Value::as_str).unwrap_or("");
365 let value = section.get("value").and_then(Value::as_str).unwrap_or("");
366 if !label.is_empty() && value.is_empty() {
367 group = label.to_string();
368 }
369 continue;
370 }
371 if kind != "metric" {
372 continue;
373 }
374 let raw_label = section.get("label").and_then(Value::as_str).unwrap_or("");
375 let field_group = section.get("group").and_then(Value::as_str).unwrap_or("");
380 let effective_group: &str = if field_group.is_empty() {
381 group.as_str()
382 } else {
383 field_group
384 };
385 let label = metric_label(id, raw_label);
386 let label = if effective_group.is_empty() {
387 label
388 } else {
389 format!("{label} ({effective_group})")
390 };
391 let percent = section.get("percent").and_then(Value::as_f64);
392 let value = section
393 .get("value")
394 .and_then(Value::as_str)
395 .filter(|value| !value.trim().is_empty())
396 .map(str::to_string)
397 .or_else(|| percent.map(|percent| format!("{}%", percent.round() as i64)))
398 .unwrap_or_default();
399 let value_headline = section.get("headline").and_then(Value::as_str) == Some("value");
400 let fraction = (percent.unwrap_or(0.0) / 100.0).clamp(0.0, 1.0);
401 let left_value = percent
402 .filter(|_| !value_headline)
403 .map(|percent| format!("{}%", (100.0 - percent).max(0.0).round() as i64));
404 let mut key = metric_key(id, raw_label, effective_group);
405 let count = seen.entry(key.clone()).or_insert(0);
406 *count += 1;
407 if *count > 1 {
408 key = format!("{key} #{count}");
409 }
410 rows.push(StripMetric {
411 provider_id: id.to_string(),
412 provider_name: name.to_string(),
413 key,
414 label,
415 value,
416 fraction,
417 bounded: true,
418 left_value,
419 left_fraction: percent.map(|_| 1.0 - fraction),
420 value_headline,
421 grouped: !effective_group.is_empty(),
422 });
423 }
424 rows
425}
426
427fn metric_key(entry_id: &str, raw_label: &str, group: &str) -> String {
432 let label = metric_label(entry_id, raw_label);
433 if group.is_empty() {
434 format!("metric:{label}")
435 } else {
436 format!("metric:{label} ({group})")
437 }
438}
439
440fn metric_label(entry_id: &str, label: &str) -> String {
444 let slug = entry_id
445 .split('@')
446 .next()
447 .unwrap_or(entry_id)
448 .to_ascii_lowercase();
449 if slug != "supergrok" {
450 return label.to_string();
451 }
452 let label = strip_trailing_word(label, "build credits");
453 strip_trailing_word(&label, "usage")
454}
455
456fn strip_trailing_word(label: &str, word: &str) -> String {
460 if label.len() > word.len() && label.to_ascii_lowercase().ends_with(word) {
461 let head = &label[..label.len() - word.len()];
462 let trimmed = head.trim_end();
463 if trimmed.len() < head.len() {
464 return trimmed.to_string();
465 }
466 }
467 label.to_string()
468}
469
470#[derive(Debug, Clone, Copy)]
472pub struct BarsLayout {
473 pub n: usize,
474 pub track_x: f64,
475 pub track_w: f64,
476 pub track_h: f64,
477 pub rx: f64,
478 pub y_offset: f64,
479 pub gap: f64,
480}
481
482pub fn bars_layout(count: usize, side: f64) -> BarsLayout {
483 let n = count.clamp(1, MAX_BARS);
484 let pad = (side * 0.08).round().max(1.0);
485 let gap = (side * 0.03).round().max(1.0);
486 let track_x = pad;
487 let track_w = side - 2.0 * pad;
488 let layout_n = n.max(2) as f64;
489 let track_h = ((side - 2.0 * pad - (layout_n - 1.0) * gap) / layout_n)
490 .floor()
491 .max(1.0);
492 let rx = (track_h / 3.0).floor().max(1.0);
493 let total_h = n as f64 * track_h + (n as f64 - 1.0) * gap;
494 let y_offset = pad + ((side - 2.0 * pad - total_h) / 2.0).floor();
495 BarsLayout {
496 n,
497 track_x,
498 track_w,
499 track_h,
500 rx,
501 y_offset,
502 gap,
503 }
504}
505
506pub fn bars_rgba(fractions: &[f64], side: u32) -> Vec<u8> {
509 let side = side.max(8);
510 let mut buf = vec![0u8; side as usize * side as usize * 4];
511 if fractions.is_empty() {
512 return buf;
513 }
514 let size = f64::from(side);
515 let layout = bars_layout(fractions.len(), size);
516 let n = layout.n;
517
518 for (i, fraction) in fractions.iter().copied().take(n).enumerate() {
519 let y = layout.y_offset + i as f64 * (layout.track_h + layout.gap) + 1.0;
520 stamp_round_rect(
521 &mut buf,
522 side,
523 RoundBar {
524 x: layout.track_x,
525 y,
526 w: layout.track_w,
527 h: layout.track_h,
528 r_left: layout.rx,
529 r_right: layout.rx,
530 },
531 41, );
533 let fill = bar_fill(layout.track_w, fraction);
534 if fill.fill_w > 0.0 {
535 let trailing = if fill.fill_w >= layout.track_w {
536 layout.rx
537 } else {
538 (layout.rx * 0.35).floor().max(0.0)
539 };
540 stamp_round_rect(
541 &mut buf,
542 side,
543 RoundBar {
544 x: layout.track_x,
545 y,
546 w: fill.fill_w,
547 h: layout.track_h,
548 r_left: layout.rx,
549 r_right: trailing,
550 },
551 255,
552 );
553 }
554 if fill.fill_w > 0.0
555 && fill.remainder_w > 0.0
556 && let Some(divider_x) = fill.divider_x
557 {
558 stamp_round_rect(
559 &mut buf,
560 side,
561 RoundBar {
562 x: layout.track_x + divider_x,
563 y,
564 w: fill.remainder_w,
565 h: layout.track_h,
566 r_left: (layout.rx * 0.2).floor().max(0.0),
567 r_right: layout.rx,
568 },
569 61, );
571 }
572 }
573 buf
574}
575
576struct RoundBar {
577 x: f64,
578 y: f64,
579 w: f64,
580 h: f64,
581 r_left: f64,
582 r_right: f64,
583}
584
585fn stamp_round_rect(buf: &mut [u8], side: u32, bar: RoundBar, alpha: u8) {
586 if bar.w <= 0.0 || bar.h <= 0.0 {
587 return;
588 }
589 let side_i = side as i32;
590 let min_x = bar.x.floor().max(0.0) as i32;
591 let min_y = bar.y.floor().max(0.0) as i32;
592 let max_x = (bar.x + bar.w).ceil().min(f64::from(side)) as i32;
593 let max_y = (bar.y + bar.h).ceil().min(f64::from(side)) as i32;
594 let target = f64::from(alpha);
595 for py in min_y..max_y {
596 for px in min_x..max_x {
597 if px < 0 || py < 0 || px >= side_i || py >= side_i {
598 continue;
599 }
600 let cover = sample_round_rect(f64::from(px) + 0.5, f64::from(py) + 0.5, &bar);
601 if cover <= 0.0 {
602 continue;
603 }
604 let idx = ((py as u32 * side + px as u32) * 4) as usize;
605 let stamped = (target * cover).round().clamp(0.0, 255.0) as u8;
606 if stamped > buf[idx + 3] {
607 buf[idx] = 0;
608 buf[idx + 1] = 0;
609 buf[idx + 2] = 0;
610 buf[idx + 3] = stamped;
611 }
612 }
613 }
614}
615
616fn sample_round_rect(px: f64, py: f64, bar: &RoundBar) -> f64 {
617 (0.5 - sd_round_box(px, py, bar)).clamp(0.0, 1.0)
618}
619
620fn sd_round_box(px: f64, py: f64, bar: &RoundBar) -> f64 {
621 let r = if px < bar.x + bar.w * 0.5 {
622 bar.r_left
623 } else {
624 bar.r_right
625 };
626 let r = r.max(0.0).min(bar.h * 0.5).min(bar.w * 0.5);
627 let cx = bar.x + bar.w * 0.5;
628 let cy = bar.y + bar.h * 0.5;
629 let dx = (px - cx).abs() - (bar.w * 0.5 - r);
630 let dy = (py - cy).abs() - (bar.h * 0.5 - r);
631 let outside = (dx.max(0.0).powi(2) + dy.max(0.0).powi(2)).sqrt();
632 outside + dx.min(0.0).max(dy.min(0.0)) - r
633}
634
635#[cfg(test)]
636mod tests {
637 use super::*;
638 use serde_json::json;
639
640 #[test]
641 fn zero_or_negative_fraction_draws_nothing() {
642 assert_eq!(bar_fill(100.0, 0.0).fill_w, 0.0);
643 assert_eq!(bar_fill(100.0, -0.5).fill_w, 0.0);
644 }
645
646 #[test]
647 fn full_fraction_fills_track_with_no_remainder() {
648 let fill = bar_fill(100.0, 1.0);
649 assert_eq!(fill.fill_w, 100.0);
650 assert_eq!(fill.remainder_w, 0.0);
651 assert_eq!(fill.divider_x, None);
652 }
653
654 #[test]
655 fn near_full_keeps_a_visible_tail() {
656 let fill = bar_fill(100.0, 0.97);
657 assert!(fill.fill_w < 100.0);
658 assert!(fill.remainder_w >= 20.0);
659 assert_eq!(fill.divider_x, Some(fill.fill_w));
660 }
661
662 #[test]
663 fn visual_fraction_quantizes_near_full_in_fifteen_percent_steps() {
664 assert!((visual_fraction(0.97) - 0.85).abs() < 0.0001);
665 assert_eq!(visual_fraction(1.0), 1.0);
666 assert_eq!(visual_fraction(0.0), 0.0);
667 assert!((visual_fraction(0.5) - 0.5).abs() < 0.0001);
668 }
669
670 fn sample_payload() -> Value {
671 json!({
672 "entries": [
673 {
674 "id": "anthropic",
675 "display_name": "Claude",
676 "status": "ready",
677 "sections": [
678 {"type": "metric", "label": "Weekly", "percent": 19, "value": "19%"},
679 {"type": "metric", "label": "Session", "percent": 41, "value": "41%"}
680 ]
681 },
682 {
683 "id": "openai",
684 "display_name": "Codex",
685 "status": "ready",
686 "sections": [
687 {"type": "metric", "label": "Codex weekly", "percent": 2, "value": "2%"}
688 ]
689 },
690 {
691 "id": "cursor",
692 "display_name": "Cursor",
693 "status": "error",
694 "sections": [
695 {"type": "metric", "label": "Cursor Models", "percent": 90, "value": "90%"}
696 ]
697 }
698 ]
699 })
700 }
701
702 #[test]
705 fn metric_keys_match_the_popover_fixture() {
706 let fixture: Value =
707 serde_json::from_str(include_str!("../../tests/fixtures/strip_metric_keys.json"))
708 .expect("fixture is JSON");
709 let cases = fixture["cases"].as_array().expect("cases");
710 assert!(!cases.is_empty());
711 for case in cases {
712 let id = case["id"].as_str().unwrap();
713 let label = case["label"].as_str().unwrap();
714 let group = case["group"].as_str().unwrap_or("");
715 assert_eq!(
716 metric_key(id, label, group),
717 case["key"].as_str().unwrap(),
718 "{case}"
719 );
720 }
721 }
722
723 #[test]
727 fn a_starred_supergrok_meter_is_painted() {
728 let payload = serde_json::json!({ "entries": [
729 { "id": "supergrok", "status": "ready", "sections": [
730 { "type": "metric", "label": "Weekly usage", "percent": 3 }
731 ]}
732 ]});
733 let mut stars = Stars::new();
734 stars.insert("supergrok".into(), vec!["metric:Weekly".into()]);
735 let content = content_from_payload(&payload, &stars, &["supergrok".into()]);
736 assert_eq!(content.bars.len(), 1);
737 assert_eq!(content.bars[0].key, "metric:Weekly");
738 }
739
740 #[test]
741 fn empty_stars_fall_back_to_first_two_bounded_metrics() {
742 let content = content_from_payload(&sample_payload(), &Stars::new(), &[]);
743 assert_eq!(content.groups.len(), 2);
744 assert_eq!(content.groups[0].2.len(), 2);
745 assert_eq!(content.groups[0].2[0].key, "metric:Weekly");
746 assert_eq!(content.groups[0].2[1].key, "metric:Session");
747 assert_eq!(content.groups[1].2[0].key, "metric:Codex weekly");
748 assert_eq!(content.bars.len(), 3);
749 assert!(!content.title_line().is_empty());
750 }
751
752 #[test]
753 fn stars_select_named_metrics_and_skip_errors() {
754 let mut stars = Stars::new();
755 stars.insert("anthropic".into(), vec!["metric:Session".into()]);
756 stars.insert("cursor".into(), vec!["metric:Cursor Models".into()]);
757 let content = content_from_payload(&sample_payload(), &stars, &[]);
758 assert_eq!(content.groups.len(), 1);
759 assert_eq!(content.groups[0].2[0].key, "metric:Session");
760 assert_eq!(content.groups[0].2[0].fraction, 0.41);
761 assert_eq!(content.bars.len(), 1);
762 }
763
764 #[test]
765 fn grouped_metric_keys_match_the_popover() {
766 let payload = json!({
767 "entries": [{
768 "id": "antigravity",
769 "display_name": "Antigravity",
770 "status": "ready",
771 "sections": [
772 {"type": "text", "label": "Session", "value": ""},
773 {"type": "metric", "label": "Gemini", "percent": 4, "value": "4%"},
774 {"type": "text", "label": "Weekly", "value": ""},
775 {"type": "metric", "label": "Gemini", "percent": 11, "value": "11%"}
776 ]
777 }]
778 });
779 let content = content_from_payload(&payload, &Stars::new(), &[]);
780 assert_eq!(content.groups[0].2[0].key, "metric:Gemini (Session)");
781 assert_eq!(content.groups[0].2[1].key, "metric:Gemini (Weekly)");
782 }
783
784 #[test]
789 fn metric_level_groups_key_like_positional_ones() {
790 let payload = json!({
791 "entries": [{
792 "id": "anthropic",
793 "display_name": "Claude",
794 "status": "ready",
795 "sections": [
796 {"type": "metric", "label": "Weekly (7d)", "percent": 32, "value": "32%"},
797 {"type": "metric", "label": "ship the release", "percent": 91,
798 "value": "91%", "group": "Sessions"},
799 {"type": "metric", "label": "Grok Build", "percent": 94,
800 "value": "94%", "group": "Breakdown"}
801 ]
802 }]
803 });
804 let mut stars = Stars::new();
805 stars.insert(
806 "anthropic".into(),
807 vec![
808 "metric:ship the release (Sessions)".into(),
809 "metric:Grok Build (Breakdown)".into(),
810 ],
811 );
812 let content = content_from_payload(&payload, &stars, &[]);
813 let bars = &content.groups[0].2;
814 assert_eq!(bars.len(), 2);
815 assert_eq!(bars[0].key, "metric:ship the release (Sessions)");
816 assert_eq!(bars[0].label, "ship the release (Sessions)");
817 assert_eq!(bars[1].key, "metric:Grok Build (Breakdown)");
818 assert_eq!(bars[1].label, "Grok Build (Breakdown)");
819 }
820
821 #[test]
824 fn left_value_is_the_remaining_percent() {
825 let payload = json!({"entries":[{"id":"zai", "status":"ready", "sections":[
826 {"type":"metric", "label":"Weekly", "percent":18, "value":"18%"},
827 {"type":"metric", "label":"Over", "percent":130, "value":"130%"},
828 {"type":"metric", "label":"Credits", "percent":40, "value":"$4 of $10", "headline":"value"},
829 {"type":"metric", "label":"Balance", "value":"$40"}]}]});
830 let (_, _, metrics) = quota_group(&payload, "zai", &[]).unwrap();
831 let left: Vec<Option<&str>> = metrics.iter().map(|m| m.left_value.as_deref()).collect();
832 assert_eq!(left, vec![Some("82%"), Some("0%"), None, None]);
833 }
834
835 #[test]
839 fn quota_group_leaves_out_hidden_metrics() {
840 let payload = json!({"entries":[{"id":"zai", "status":"ready", "sections":[
841 {"type":"metric", "label":"Session (5h)", "percent":0, "value":"0%"},
842 {"type":"metric", "label":"MCP tools (monthly)", "percent":18, "value":"18%"},
843 {"type":"metric", "label":"Glm", "group":"Models", "percent":40, "value":"40%"}]}]});
844 let keys = |hidden: &[&str]| {
845 let hidden: Vec<String> = hidden.iter().map(|k| k.to_string()).collect();
846 quota_group(&payload, "zai", &hidden)
847 .map(|(_, _, metrics)| metrics.into_iter().map(|m| m.key).collect::<Vec<_>>())
848 };
849
850 assert_eq!(
851 keys(&[]),
852 Some(vec![
853 "metric:Session (5h)".to_string(),
854 "metric:MCP tools (monthly)".to_string()
855 ])
856 );
857 assert_eq!(
858 keys(&["metric:MCP tools (monthly)"]),
859 Some(vec!["metric:Session (5h)".to_string()])
860 );
861 assert_eq!(
862 keys(&["metric:Session (5h)", "metric:MCP tools (monthly)"]),
863 Some(vec!["metric:Glm (Models)".to_string()])
864 );
865 assert_eq!(
866 keys(&[
867 "metric:Session (5h)",
868 "metric:MCP tools (monthly)",
869 "metric:Glm (Models)"
870 ]),
871 None
872 );
873 }
874
875 #[test]
878 fn parse_hidden_rows_reads_the_strip_ipc() {
879 let hidden = parse_hidden_rows(&json!({"hidden_rows": {
880 "zai": [" metric:MCP tools (monthly) ", "metric:MCP tools (monthly)", "", 7],
881 "": ["metric:Weekly"],
882 "openai": [],
883 "kimi": "metric:Weekly"
884 }}));
885 assert_eq!(hidden.len(), 1);
886 assert_eq!(
887 hidden["zai"],
888 vec!["metric:MCP tools (monthly)".to_string()]
889 );
890 assert!(parse_hidden_rows(&json!({"stars": {}})).is_empty());
891 assert!(parse_hidden_rows(&json!({"hidden_rows": ["zai"]})).is_empty());
892 }
893
894 #[test]
895 fn parse_strip_ipc_caps_stars_at_two_per_provider() {
896 let value = json!({
897 "style": "text",
898 "stars": {
899 "anthropic": ["metric:Weekly", "metric:Session", "metric:Extra"],
900 "": ["x"],
901 "openai": ["metric:Codex weekly"]
902 }
903 });
904 let (style, stars, order) = parse_strip_ipc(&value);
905 assert_eq!(style, StripStyle::Bars);
906 assert_eq!(stars["anthropic"].len(), 2);
907 assert_eq!(stars["openai"], vec!["metric:Codex weekly".to_string()]);
908 assert!(!stars.contains_key(""));
909 assert!(order.is_empty());
910 }
911
912 #[test]
913 fn strip_follows_popover_card_order() {
914 let mut stars = Stars::new();
915 stars.insert("anthropic".into(), vec!["metric:Weekly".into()]);
916 stars.insert("openai".into(), vec!["metric:Codex weekly".into()]);
917 let content = content_from_payload(
918 &sample_payload(),
919 &stars,
920 &["openai".into(), "anthropic".into()],
921 );
922 assert_eq!(content.groups[0].0, "openai");
923 assert_eq!(content.groups[1].0, "anthropic");
924 assert_eq!(content.bars[0].provider_id, "openai");
925 assert_eq!(content.bars[1].provider_id, "anthropic");
926 }
927
928 #[test]
929 fn bars_rgba_is_square_black_and_anti_aliased() {
930 let bytes = bars_rgba(&[0.4, 0.97], BARS_PIXEL_SIDE);
931 assert_eq!(
932 bytes.len(),
933 (BARS_PIXEL_SIDE * BARS_PIXEL_SIDE * 4) as usize
934 );
935 assert_eq!(bytes[3], 0, "top-left stays transparent");
936 let last = bytes.len() - 1;
937 assert_eq!(bytes[last], 0, "bottom-right stays transparent");
938 let mut inked = 0usize;
939 let mut soft = 0usize;
940 for pixel in bytes.as_chunks::<4>().0 {
941 if pixel[3] > 0 {
942 inked += 1;
943 assert_eq!(&pixel[..3], &[0, 0, 0]);
944 }
945 if pixel[3] > 0 && pixel[3] < 255 {
946 soft += 1;
947 }
948 }
949 assert!(inked > 40, "glyph too sparse: {inked}");
950 assert!(soft > 0, "expected anti-aliased edges");
951 }
952
953 #[test]
954 fn empty_fractions_are_fully_transparent() {
955 let bytes = bars_rgba(&[], BARS_PIXEL_SIDE);
956 assert!(bytes.as_chunks::<4>().0.iter().all(|p| p[3] == 0));
957 }
958
959 #[test]
960 fn bars_rgba_at_each_status_item_scale() {
961 for scale in BARS_SCALES {
962 let side = BARS_POINT_SIDE * scale;
963 let bytes = bars_rgba(&[0.4, 0.97], side);
964 assert_eq!(bytes.len(), (side * side * 4) as usize, "scale {scale}");
965 assert!(
966 bytes.as_chunks::<4>().0.iter().any(|p| p[3] > 0),
967 "scale {scale} produced an empty glyph"
968 );
969 }
970 }
971}