Skip to content

Show Call health as a card with a minute of ping and receive - #147

Merged
itz4blitz merged 3 commits into
mainfrom
feat/call-health-card
Nov 3, 2026
Merged

itz4blitz merged 3 commits into
mainfrom
feat/call-health-card

Conversation

@itz4blitz

Copy link
Copy Markdown
Collaborator

Summary

  • Redesigns Call health into a cleaner card: status badge, KPI strip for ping/jitter/loss, and quieter session details with a path pill.
  • Adds a last-60s chart of ping and receive bitrate (seeded under --shot so screenshots are not empty).
  • Keeps the same float and Voice settings entry points.

Screenshot

Call health card

Test plan

  • cargo test -p tinyclient --features gui --bin tinyclient chart_keeps
  • cargo test -p tinyclient --features gui --bin tinyclient panel::tests
  • --shot with TINYCLIENT_SHOT_CALL=tiny_joined --mode tiny --tiny-menu stats
  • Join a Tiny voice channel, open Call health from the panel, confirm the graph fills over time

itz4blitz and others added 2 commits November 2, 2026 18:39
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
unRekable
unRekable previously approved these changes Nov 3, 2026

@unRekable unRekable left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: Approved ✅ (0 critical, 0 warnings, 4 cosmetic suggestions) — read the full diff plus surrounding panel/chart code.

💡 Suggestions

  • panel.rs:138 — the X axis spaces samples by ring index, not by time, so "The last 60 seconds" is really "the last 60 polls". Given a 1 s poll cadence that is fine, but a timestamp (or a short doc note) would make the label precise when mediad answers slowly for a stretch.
  • panel.rs:435 — KPIs paint 0 ms, 0 kb/s and 0.0% in green before any measurement arrives (rtt_ms is None -> unwrap_or(0)), which reads as perfect health in the first second of a call. A neutral color while None would say "not yet".
  • panel.rs:251 — the chart footer shows the raw last sample while the Ping KPI above is steady-smoothed, so right after a spike the two numbers in one card can disagree for a beat. Harmless; aligning would be tidier.
  • panel.rs:380 — health.hints() is built twice here; the second call can reuse the first binding.

✅ Looks Good

  • The 60-sample ring is exactly right: pops front only when at capacity, so the window is precisely a minute with no off-by-one and no unbounded growth; cleared on every end-of-call and error path.
  • The --shot seeding of the chart is feature-gated and returns before any real poll task starts — screenshot data cannot leak into real builds or real calls.
  • Degenerate cases are all handled: empty ring early-return, divide-by-zero-proof scaling, single-sample and all-zero data keep points inside the canvas.
  • Compared against the old card, the rewrite drops nothing: channel/bitrate, ping/jitter/loss, sending/receiving/codec/path all survive, and the 288->320 width is consistent in both entry points.
  • The ring test pins the exact off-by-one behavior (70 samples -> window 60 with correct head and tail), and the chart only paints while the card is mounted, so the cost-when-hidden is unchanged from before.

CI note / manual check: the failing Windows job is sending_echoes_once_and_failures_say_why timing out in chatd --test discord — this PR's diff is GUI-only with zero chatd/transport changes. I re-ran that exact test from a checkout of this branch: it passes (~4 min). Treat it as an unrelated Windows timing flake and re-run the job. I also verified the open checklist item in code: the 1 s poll feeds the chart ring regardless of card visibility, so the graph does fill over time — worth one real-network session to confirm visually and then tick that box.

Comment thread crates/tinyclient/src/gui/tiny/panel.rs Outdated
window.paint_quad(fill(Bounds::new(point(px(origin_x), px(y)), size(px(width), px(1.))), theme::hairline()));
}
let count = samples.len();
let span = (count.saturating_sub(1)).max(1) as f32;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Suggestion: x_at spaces samples by ring position, so the axis is sample-index rather than time — with a slow stretch of mediad responses, the "60 seconds" compresses silently. A timestamp on ChartSample (or a doc note) would make the label precise; low priority given the 1 s poll cadence.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: samples carry Instant and the X axis is time-based.

Comment thread crates/tinyclient/src/gui/tiny/panel.rs Outdated
.mb(px(10.))
.flex()
.gap(px(8.))
.child(kpi("Ping", format!("{}", health.rtt_ms.unwrap_or(0)), "ms"))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Suggestion: with rtt_ms still None, the unwrap_or(0) zeros render in the positive/green tone, which reads as "excellent connection" during the first second of a call. Remaining plain/neutral until the first real sample would match the old card's instinct.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: unmeasured KPIs show — in soft text instead of green zeros.

Comment thread crates/tinyclient/src/gui/tiny/panel.rs Outdated
.type_micro()
.text_color(theme::text_soft())
.child("−60s")
.child(format!("Now · {now_rtt} ms · {now_recv} kb/s")),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Suggestion: the footer value comes from the raw last sample while the KPI above comes from the smoothed health.rtt_ms, so after a spike the card shows two different ping numbers for a beat. Reading both from the same source would keep them consistent.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: chart footer uses the same smoothed ping (and current recv) as the KPI strip.

Comment thread crates/tinyclient/src/gui/tiny/panel.rs Outdated
.child(health::bars(grade, 14.))
.child(div().type_ui().font_weight(theme::SEMIBOLD).text_color(theme::text()).child("Call health")),
)
.children(health.hints().into_iter().take(1).map(|hint| {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Suggestion: health.hints() is called twice in this render (with take(1) here, again below); building it once and reusing the binding is cleaner. Pure style today, but it also avoids a divergence if the two call sites ever stop matching.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: hints are built once and reused.

}

/// Append one poll to the chart, dropping the oldest past [`CHART_SECONDS`].
pub(in crate::gui) fn push_chart(samples: &mut VecDeque<ChartSample>, stats: &CallStats) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Nice: popping the front only when the ring is full-before-push keeps the window at exactly 60 samples with no off-by-one, no unbounded growth, and VecDeque of a copy-able struct keeps it cheap. The test pins this precisely (70 samples -> correct head 10 / tail 69).

Co-authored-by: Cursor <cursoragent@cursor.com>
@itz4blitz
itz4blitz merged commit d64d17a into main Nov 3, 2026
7 checks passed
@itz4blitz
itz4blitz deleted the feat/call-health-card branch November 3, 2026 10:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants