From 91c134c309ae6063c7b3dd5e6f2292393328df1f Mon Sep 17 00:00:00 2001 From: Svrnty Date: Sun, 24 May 2026 17:24:56 -0400 Subject: [PATCH] fix(adwright): read budget_total from backend (was c.budget, undefined) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend AdwrightCore.list_cycles preserves proto snake_case via MessageToDict(preserving_proto_field_name=True), so cycle JSON carries `budget_total` (decimal string like "200.00"), not `budget`. Frontend read c.budget which was always undefined โ†’ fell back to 0 in both the Cycles row meta and the Overview spend bar (which then defaulted to $6,000 budget per the `|| 6000` floor in _deriveKpis). Added _cycleBudget + _cycleSpend helpers that parseFloat the decimal strings, with c.budget fallback so any future renamed field still works. Spend stays 0 until GetCycleMetrics is wired in โ€” it doesn't live in ListCycles. Verified live: Cycles row now shows $0 / $200 (was $0 / $0). Overview spend now shows $0 / $200 (was $0 / $6,000). Co-Authored-By: Claude Opus 4.7 (1M context) --- static/adwright.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/static/adwright.js b/static/adwright.js index c0fb1c3..b00659a 100644 --- a/static/adwright.js +++ b/static/adwright.js @@ -320,11 +320,20 @@ ).join("") + ''; } + // Backend proto field for cycle budget is `budget_total` (decimal string, + // e.g. "200.00"). Spend lives in cycle_metric (fetched via GetCycleMetrics + // โ€” not in ListCycles), so it stays 0 until that wire lands. + function _cycleBudget(x) { + return parseFloat(x.budget_total || x.budget || 0) || 0; + } + function _cycleSpend(x) { + return parseFloat(x.spend || 0) || 0; + } function _deriveKpis(cycles, recipes) { const c = cycles || []; const r = recipes || []; - const spend = c.reduce((s, x) => s + (x.spend || 0), 0); - const budget = c.reduce((s, x) => s + (x.budget || 0), 0) || 6000; + const spend = c.reduce((s, x) => s + _cycleSpend(x), 0); + const budget = c.reduce((s, x) => s + _cycleBudget(x), 0) || 6000; const impressions = c.reduce((s, x) => s + (x.impressions || 0), 0); const ctrs = c.map((x) => x.ctr || 0).filter((v) => v > 0); const ctr = ctrs.length ? (ctrs.reduce((s, x) => s + x, 0) / ctrs.length) : 0; @@ -374,7 +383,7 @@ '
' + _esc(c.title || ("Cycle #" + c.id)) + '
' + '
' + _esc(c.status || "") + ' ยท started ' + _esc(c.started_at || "") + '
' + '' + - '
$' + _fmt(c.spend || 0) + ' / $' + _fmt(c.budget || 0) + '
' + + '
$' + _fmt(_cycleSpend(c)) + ' / $' + _fmt(_cycleBudget(c)) + '
' + '' ); }