fix(review): restore Agent Update Required flow, command field, axis alignment

Fixes from Jason's hands-on verification of the branch:

1. Old-agent messaging regression (this branch): a detail-request timeout
   went through the loud poison/reconnect flow, burying the ProcessDetails
   modal's 'Agent Update Required' message under a connection-error modal.
   Old agents IGNORE unknown messages (no late reply, no desync), so the
   optional per-PID endpoints now use quiet_reconnect(): swap the stream
   silently (still safe against merely-slow agents) and let the modal show
   its message. Only a failed reconnect surfaces loudly. Verified against
   a real v1.40.0 agent: message shows, session stays healthy.

2. Draw starvation (this branch): an agent that never answers get_metrics
   put the loop in fetch->timeout->poison->restart cycles that never
   reached the draw call — permanently blank TUI. The iteration now paints
   before fetching, and a second consecutive metrics timeout trips a
   circuit breaker: persistent 'Agent is not responding' error, recovery
   left to the manual/30s retry paths. Verified against a 0.9-era agent.

3. Command & Details pane blank (pre-existing on master): the minimal-
   refresh optimization dropped cmd/exe/cwd from the detail endpoint's
   ProcessRefreshKind, so process.cmd() had nothing to return. Restored
   with UpdateKind::OnlyIfNotSet — immutable values, read once per PID.
   Regression test added; journal E2E re-verified (100 entries render).

4. Scatter-plot axis misalignment: Y labels used a fixed 4-char field from
   the era when CPU times were 1000x too small; honest millisecond values
   (e.g. 136114) blew through it. Labels now right-align to the widest
   value per frame and X labels/titles share the dynamic padding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jasonwitty
2026-08-19 16:52:31 -07:00
parent bf6ac877c2
commit 746ca4cf58
4 changed files with 115 additions and 28 deletions
+8 -1
View File
@@ -1161,10 +1161,17 @@ pub async fn collect_process_metrics(
system.refresh_processes_specifics(
ProcessesToUpdate::Some(&[sysinfo::Pid::from_u32(pid)]),
false,
// cmd/exe/cwd feed the modal's Command & Details pane. They're
// immutable per process, so OnlyIfNotSet reads them once per PID and
// serves the cache afterwards — the "minimal refresh" optimization
// had dropped them entirely, leaving the pane blank.
ProcessRefreshKind::nothing()
.with_memory()
.with_cpu()
.with_disk_usage(),
.with_disk_usage()
.with_cmd(sysinfo::UpdateKind::OnlyIfNotSet)
.with_exe(sysinfo::UpdateKind::OnlyIfNotSet)
.with_cwd(sysinfo::UpdateKind::OnlyIfNotSet),
);
let process = system