From ac1c4da9d491fc045df6b85777674abbe6755058 Mon Sep 17 00:00:00 2001 From: Lakshman Date: Wed, 27 May 2026 09:24:17 -0500 Subject: [PATCH 1/2] fix(stats): render Agents-running sparkline immediately on first paint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fsb-agents-running chart fed Chart.js a 1-point line dataset for the first 5 minutes after mount: agentHistoryRing starts empty, FSBTelemetryService polls every 5 min, so only the immediate-fetch sample existed until the next poll. With pointRadius:0 (sparkline aesthetic) a 1-point line draws literally nothing — Chart.js needs >=2 points to stroke. Net effect: visitors landed on /stats, switched to "Agents running", and saw a 360px void for 5 minutes. Fix: render-time fallback inside the fsb-agents-running switch case only. When ring.length < 2, synthesize [live, live] from the current active_agents_now headline value so the chart draws a flat "currently N agents" line from the first paint, gaining real shape as the ring fills. Polling cadence, ring buffer, legend i18n marker, and all other tabs untouched. Scoped to lines 1029-1059 of stats-page.component.ts. messages.xlf line-number context drift regenerated via ng extract-i18n. --- .../app/pages/stats/stats-page.component.ts | 18 ++++++++++++++++-- showcase/angular/src/locale/messages.xlf | 8 ++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/showcase/angular/src/app/pages/stats/stats-page.component.ts b/showcase/angular/src/app/pages/stats/stats-page.component.ts index 04d2b86dc..54b33603a 100644 --- a/showcase/angular/src/app/pages/stats/stats-page.component.ts +++ b/showcase/angular/src/app/pages/stats/stats-page.component.ts @@ -1029,17 +1029,31 @@ export class StatsPageComponent implements OnInit, AfterViewInit, OnDestroy { // 288 samples = 24h @ 5-min poll). slice() copies so Chart.js can't // mutate the canonical buffer. Axes hidden so the line reads as a // sparkline; bucket label still appears in the legend for context. + // + // First-paint warmup (bug fix 260527): the ring buffer starts empty + // and only grows by one sample per 5-min poll. A 1-point line dataset + // with pointRadius:0 draws NOTHING -- Chart.js needs >=2 points to + // stroke a line, and we hide point markers for the sparkline aesthetic. + // Result: the canvas was visually empty for ~5 minutes after page load + // until the second poll arrived. Fix: when ring.length < 2, synthesize + // a 2-point flat-line dataset at the current `active_agents_now` + // headline value so the chart immediately reads as "currently N agents" + // and gains real shape as the ring fills with subsequent samples. The + // ring buffer itself is untouched -- this is a render-time fallback + // scoped strictly to this case. const headline = this.latestFsbHeadline; const bucket = headline?.active_agents_bucket ?? '0'; const ring = this.agentHistoryRing.slice(); + const live = headline?.active_agents_now ?? 0; + const data = ring.length >= 2 ? ring : [live, live]; return { type: 'line', data: { - labels: ring.map((_, i) => String(i)), + labels: data.map((_, i) => String(i)), datasets: [ { label: $localize`:@@SHOWCASE_STATS_FSB_CHART_AGENTS_RUNNING_LEGEND:Active agents (10 min window)` + ` [${bucket}]`, - data: ring, + data, borderColor: tokens.primary, backgroundColor: tokens.primarySoft, fill: true, diff --git a/showcase/angular/src/locale/messages.xlf b/showcase/angular/src/locale/messages.xlf index cb2ebeb05..c5aec656f 100644 --- a/showcase/angular/src/locale/messages.xlf +++ b/showcase/angular/src/locale/messages.xlf @@ -3030,28 +3030,28 @@ Active agents (10 min window) src/app/pages/stats/stats-page.component.ts - 1041 + 1055 Pending (k>=5 floor) src/app/pages/stats/stats-page.component.ts - 1070 + 1084 Popular agents src/app/pages/stats/stats-page.component.ts - 1077 + 1091 Avg agents per active user (last 30 days) src/app/pages/stats/stats-page.component.ts - 1109 + 1123 From 578aca810ca7f1b6f0b4940a6be33da0396f5ffe Mon Sep 17 00:00:00 2001 From: Lakshman Date: Wed, 27 May 2026 09:32:58 -0500 Subject: [PATCH 2/2] fix(stats): gate sparkline fallback on real FSB headline (Codex P2) Codex review on PR #85 flagged that when GitHub datasets flip viewState to 'ready' before the FSB headline endpoint resolves (or when it errors), the first-paint fallback synthesizes [0, 0] -- a misleading flat-zero line as if there were genuinely zero active agents. Add a `!headline` gate around the fallback so the canvas stays empty until at least one real FSB sample arrives. Once latestFsbHeadline is non-null, onFsbHeadlineUpdate has atomically pushed onto the ring, so the live-value seed is always backed by a real reading. messages.xlf line-number context drift regenerated via ng extract-i18n. --- .../app/pages/stats/stats-page.component.ts | 23 +++++++++++++------ showcase/angular/src/locale/messages.xlf | 8 +++---- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/showcase/angular/src/app/pages/stats/stats-page.component.ts b/showcase/angular/src/app/pages/stats/stats-page.component.ts index 54b33603a..bfd3e4351 100644 --- a/showcase/angular/src/app/pages/stats/stats-page.component.ts +++ b/showcase/angular/src/app/pages/stats/stats-page.component.ts @@ -1035,17 +1035,26 @@ export class StatsPageComponent implements OnInit, AfterViewInit, OnDestroy { // with pointRadius:0 draws NOTHING -- Chart.js needs >=2 points to // stroke a line, and we hide point markers for the sparkline aesthetic. // Result: the canvas was visually empty for ~5 minutes after page load - // until the second poll arrived. Fix: when ring.length < 2, synthesize - // a 2-point flat-line dataset at the current `active_agents_now` - // headline value so the chart immediately reads as "currently N agents" - // and gains real shape as the ring fills with subsequent samples. The - // ring buffer itself is untouched -- this is a render-time fallback - // scoped strictly to this case. + // until the second poll arrived. Fix: when ring.length < 2 AND we have + // a real FSB headline, synthesize a 2-point flat-line dataset at the + // current `active_agents_now` value so the chart immediately reads as + // "currently N agents" and gains real shape as the ring fills. + // + // Gate on `headline` (per Codex P2 review): GitHub datasets can flip + // viewState to 'ready' before the FSB headline endpoint resolves (or + // when it errors). Without this gate, the fallback would draw a + // misleading [0, 0] flat-zero line during that window -- as if there + // were genuinely zero active agents. Better to render an empty canvas + // (honest "no data yet") until at least one real FSB sample arrives. + // Note: onFsbHeadlineUpdate assigns `latestFsbHeadline` and pushes onto + // the ring atomically, so `headline !== null` implies `ring.length >= 1`. const headline = this.latestFsbHeadline; const bucket = headline?.active_agents_bucket ?? '0'; const ring = this.agentHistoryRing.slice(); const live = headline?.active_agents_now ?? 0; - const data = ring.length >= 2 ? ring : [live, live]; + const data = !headline + ? [] + : ring.length >= 2 ? ring : [live, live]; return { type: 'line', data: { diff --git a/showcase/angular/src/locale/messages.xlf b/showcase/angular/src/locale/messages.xlf index c5aec656f..0b5b4f3bd 100644 --- a/showcase/angular/src/locale/messages.xlf +++ b/showcase/angular/src/locale/messages.xlf @@ -3030,28 +3030,28 @@ Active agents (10 min window) src/app/pages/stats/stats-page.component.ts - 1055 + 1064 Pending (k>=5 floor) src/app/pages/stats/stats-page.component.ts - 1084 + 1093 Popular agents src/app/pages/stats/stats-page.component.ts - 1091 + 1100 Avg agents per active user (last 30 days) src/app/pages/stats/stats-page.component.ts - 1123 + 1132