diff --git a/internal/gateway/ui/index.html b/internal/gateway/ui/index.html
index b5db78d..16f8522 100644
--- a/internal/gateway/ui/index.html
+++ b/internal/gateway/ui/index.html
@@ -766,37 +766,52 @@ async function paintStats() {
const okr = tot.reqs ? Math.round(tot.ok * 100 / tot.reqs) : 0;
const avg = fmtLat(tot.latency_sum_ms, tot.reqs);
const kpi = $('#kpi-row');
- if (kpi) kpi.innerHTML = `
+ if (kpi && !kpi.dataset.built) {
+ kpi.dataset.built = '1';
+ kpi.innerHTML = `
${t('kpiActive')}
-
${st.active || 0}
+
reqs / 3s
${t('kpiReqs')}
-
${fmtN(tot.reqs)}
-
${t('kpiOk')} ${okr}%
+
+
${t('kpiTokens')}
-
${fmtTok(tot.tokens)}
-
${t('thPrompt')} ${fmtTok(tot.prompt_tokens)}${t('thCompl')} ${fmtTok(tot.completion_tokens)}
+
+
${t('kpiLat')}
-
${fmtMs(avg)}
-
${t('kpiMaxLat')} ${fmtMs(tot.latency_max_ms)}
+
+
`;
+ }
+ // update values without rebuilding the card DOM (prevents the 3s flicker)
+ const kset = (sel, txt) => { const e = $(sel); if (e) e.textContent = txt; };
+ kset('[data-kpi="active"]', st.active || 0);
+ kset('[data-kpi="reqs"]', fmtN(tot.reqs));
+ const rsub = $('[data-kpi="reqs-sub"]');
+ if (rsub) rsub.innerHTML = `${t('kpiOk')} ${okr}%`;
+ kset('[data-kpi="tokens"]', fmtTok(tot.tokens));
+ const tsub = $('[data-kpi="tokens-sub"]');
+ if (tsub) tsub.innerHTML = `${t('thPrompt')} ${fmtTok(tot.prompt_tokens)}${t('thCompl')} ${fmtTok(tot.completion_tokens)}`;
+ kset('[data-kpi="lat"]', fmtMs(avg));
+ kset('[data-kpi="lat-sub"]', `${t('kpiMaxLat')} ${fmtMs(tot.latency_max_ms)}`);
+ if (kpi) kpi.classList.toggle('kpi-exit', !!statsKeyF);
paintModelTable(st.by_model || []);
paintSrcTable(st.by_source || []);
paintStatusTable(st.by_status || []);