From 86d907a869bc9da561c2459363a9da341c0330f7 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Wed, 27 May 2026 22:56:53 +0200 Subject: [PATCH 1/3] fix(home/stats): drop /mo suffix from Period Savings total Revert the change applied by PR #763 ("CodeRabbit auto-fixes"): it appended the unit suffix to Period Savings even though the line immediately above explicitly says Period Savings is a cumulative dollar total over the selected date range, NOT a per-unit rate. The auto-fix violated its own comment and broke 4 tests in frontend/src/__tests__/savings-history.test.ts, which assert plain "$X.XX" output (no suffix). Restoring the original behaviour so the tests and the comment agree again. --- frontend/src/modules/savings-history.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/modules/savings-history.ts b/frontend/src/modules/savings-history.ts index 3c57e4c9a..c6cf6da87 100644 --- a/frontend/src/modules/savings-history.ts +++ b/frontend/src/modules/savings-history.ts @@ -289,7 +289,7 @@ function renderSavingsStats(data: SavingsAnalyticsResponse): void { if (periodSavingsEl) { // Period Savings is the cumulative total over the selected date range // (no per-unit rate suffix -- it is already a dollar total). - periodSavingsEl.textContent = formatCurrency(displayTotal) + ' ' + suffix; + periodSavingsEl.textContent = formatCurrency(displayTotal); } if (avgLabelEl) { avgLabelEl.textContent = `Avg ${adjective} Savings`; From 44801c94d568320f373168e13f8a9f6b39db85d0 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Wed, 27 May 2026 23:10:43 +0200 Subject: [PATCH 2/3] feat(home/stats): show selected unit on Period Savings label Period Savings is a cumulative dollar total over the selected date range, not a per-unit rate. Appending /mo, /hr, or /yr to the value misrepresents it as a rate. Instead, the label becomes "Period Savings (monthly)" etc., reinforcing which view mode is active without implying a rate. Added id="period-savings-label" to the

in index.html, updated renderSavingsStats to set its textContent to `Period Savings (${unit})` using the already-available adjective variable (lowercased), and added label assertions to the unit-toggle test for all three units. --- frontend/src/__tests__/savings-history.test.ts | 10 +++++++--- frontend/src/index.html | 2 +- frontend/src/modules/savings-history.ts | 9 +++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/frontend/src/__tests__/savings-history.test.ts b/frontend/src/__tests__/savings-history.test.ts index 3d195c76e..5d8b2390c 100644 --- a/frontend/src/__tests__/savings-history.test.ts +++ b/frontend/src/__tests__/savings-history.test.ts @@ -65,6 +65,7 @@ describe('Savings History Module', () => {

Data will be collected hourly once you have active purchases.

+

Period Savings

$0

Avg Monthly Savings

$0/mo @@ -1278,21 +1279,24 @@ describe('Savings History Module', () => { test('period savings (cumulative total) also converts with unit toggle', async () => { (getSavingsAnalytics as jest.Mock).mockResolvedValue(mockData); - // Monthly + // Monthly: value is clean dollar total; label reflects view mode await loadSavingsHistory(); const periodElMonthly = document.getElementById('period-savings'); expect(periodElMonthly?.textContent).toBe('$730.00'); + expect(document.getElementById('period-savings-label')?.textContent).toContain('monthly'); - // Hourly: 730 / 730 = 1.00 + // Hourly: 730 / 730 = 1.00; label updates to 'hourly' const unitSelect = document.getElementById('savings-unit') as HTMLSelectElement; unitSelect.value = 'hourly'; await loadSavingsHistory(); expect(document.getElementById('period-savings')?.textContent).toBe('$1.00'); + expect(document.getElementById('period-savings-label')?.textContent).toContain('hourly'); - // Yearly: 730 * 12 = 8760 -> $8.76K + // Yearly: 730 * 12 = 8760 -> $8.76K; label updates to 'yearly' unitSelect.value = 'yearly'; await loadSavingsHistory(); expect(document.getElementById('period-savings')?.textContent).toBe('$8.76K'); + expect(document.getElementById('period-savings-label')?.textContent).toContain('yearly'); }); test('chart tooltip uses selected unit suffix for period savings dataset', async () => { diff --git a/frontend/src/index.html b/frontend/src/index.html index 4a6da5e38..9777d988b 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -175,7 +175,7 @@

Savings History

-

Period Savings

+

Period Savings

$0.00

diff --git a/frontend/src/modules/savings-history.ts b/frontend/src/modules/savings-history.ts index c6cf6da87..d2697055d 100644 --- a/frontend/src/modules/savings-history.ts +++ b/frontend/src/modules/savings-history.ts @@ -248,6 +248,12 @@ function getPeriodDates(period: string): { start: Date; end: Date; interval: 'ho */ function renderSavingsStats(data: SavingsAnalyticsResponse): void { const periodSavingsEl = document.getElementById('period-savings'); + // The unit indicator belongs on the label, not appended to the value. + // Period Savings is a cumulative dollar total over the selected date range, + // not a per-unit rate -- appending /mo, /hr, /yr would misrepresent it as + // a rate. The label "Period Savings (monthly)" shows which view mode is + // active without implying a rate. + const periodSavingsLabelEl = document.getElementById('period-savings-label'); const avgSavingsEl = document.getElementById('avg-hourly-savings'); const peakSavingsEl = document.getElementById('peak-savings'); const avgLabelEl = document.getElementById('avg-savings-label'); @@ -291,6 +297,9 @@ function renderSavingsStats(data: SavingsAnalyticsResponse): void { // (no per-unit rate suffix -- it is already a dollar total). periodSavingsEl.textContent = formatCurrency(displayTotal); } + if (periodSavingsLabelEl) { + periodSavingsLabelEl.textContent = `Period Savings (${adjective.toLowerCase()})`; + } if (avgLabelEl) { avgLabelEl.textContent = `Avg ${adjective} Savings`; } From c3587143f4c640b754db5d453e5a4a8efd94d71a Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Wed, 27 May 2026 23:33:46 +0200 Subject: [PATCH 3/3] feat(home/stats): show unit below value on Period Savings, not in label The label ("Period Savings") answers "what is this number" and should stay plain across all unit modes. A new sub-line element (#period-savings-unit, class stat-unit-context) below the dollar value carries the view-mode context ("shown in monthly/hourly/yearly equivalents"), making the semantic layering explicit: label = what, value = number, sub-line = how it is displayed. Updated the DOM fixture and three unit-toggle assertions in savings-history.test.ts to query #period-savings-unit. Added .stat-unit-context CSS rule to charts.css. --- frontend/src/__tests__/savings-history.test.ts | 13 +++++++------ frontend/src/index.html | 1 + frontend/src/modules/savings-history.ts | 14 +++++++++----- frontend/src/styles/charts.css | 6 ++++++ 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/frontend/src/__tests__/savings-history.test.ts b/frontend/src/__tests__/savings-history.test.ts index 5d8b2390c..70c9f93a9 100644 --- a/frontend/src/__tests__/savings-history.test.ts +++ b/frontend/src/__tests__/savings-history.test.ts @@ -67,6 +67,7 @@ describe('Savings History Module', () => {

Period Savings

$0 +

shown in monthly equivalents

Avg Monthly Savings

$0/mo $0/mo @@ -1279,24 +1280,24 @@ describe('Savings History Module', () => { test('period savings (cumulative total) also converts with unit toggle', async () => { (getSavingsAnalytics as jest.Mock).mockResolvedValue(mockData); - // Monthly: value is clean dollar total; label reflects view mode + // Monthly: value is clean dollar total; sub-line reflects view mode await loadSavingsHistory(); const periodElMonthly = document.getElementById('period-savings'); expect(periodElMonthly?.textContent).toBe('$730.00'); - expect(document.getElementById('period-savings-label')?.textContent).toContain('monthly'); + expect(document.getElementById('period-savings-unit')?.textContent).toContain('monthly'); - // Hourly: 730 / 730 = 1.00; label updates to 'hourly' + // Hourly: 730 / 730 = 1.00; sub-line updates to 'hourly' const unitSelect = document.getElementById('savings-unit') as HTMLSelectElement; unitSelect.value = 'hourly'; await loadSavingsHistory(); expect(document.getElementById('period-savings')?.textContent).toBe('$1.00'); - expect(document.getElementById('period-savings-label')?.textContent).toContain('hourly'); + expect(document.getElementById('period-savings-unit')?.textContent).toContain('hourly'); - // Yearly: 730 * 12 = 8760 -> $8.76K; label updates to 'yearly' + // Yearly: 730 * 12 = 8760 -> $8.76K; sub-line updates to 'yearly' unitSelect.value = 'yearly'; await loadSavingsHistory(); expect(document.getElementById('period-savings')?.textContent).toBe('$8.76K'); - expect(document.getElementById('period-savings-label')?.textContent).toContain('yearly'); + expect(document.getElementById('period-savings-unit')?.textContent).toContain('yearly'); }); test('chart tooltip uses selected unit suffix for period savings dataset', async () => { diff --git a/frontend/src/index.html b/frontend/src/index.html index 9777d988b..57531e07f 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -177,6 +177,7 @@

Savings History

Period Savings

$0.00

+

shown in monthly equivalents

Avg Monthly Savings

diff --git a/frontend/src/modules/savings-history.ts b/frontend/src/modules/savings-history.ts index d2697055d..a7e28f3f0 100644 --- a/frontend/src/modules/savings-history.ts +++ b/frontend/src/modules/savings-history.ts @@ -248,12 +248,13 @@ function getPeriodDates(period: string): { start: Date; end: Date; interval: 'ho */ function renderSavingsStats(data: SavingsAnalyticsResponse): void { const periodSavingsEl = document.getElementById('period-savings'); - // The unit indicator belongs on the label, not appended to the value. + // The unit indicator belongs below the value, not inside the label. // Period Savings is a cumulative dollar total over the selected date range, - // not a per-unit rate -- appending /mo, /hr, /yr would misrepresent it as - // a rate. The label "Period Savings (monthly)" shows which view mode is - // active without implying a rate. + // not a per-unit rate -- the label stays plain ("Period Savings") and the + // sub-line (#period-savings-unit) shows which view mode is active without + // implying a rate. const periodSavingsLabelEl = document.getElementById('period-savings-label'); + const periodSavingsUnitEl = document.getElementById('period-savings-unit'); const avgSavingsEl = document.getElementById('avg-hourly-savings'); const peakSavingsEl = document.getElementById('peak-savings'); const avgLabelEl = document.getElementById('avg-savings-label'); @@ -298,7 +299,10 @@ function renderSavingsStats(data: SavingsAnalyticsResponse): void { periodSavingsEl.textContent = formatCurrency(displayTotal); } if (periodSavingsLabelEl) { - periodSavingsLabelEl.textContent = `Period Savings (${adjective.toLowerCase()})`; + periodSavingsLabelEl.textContent = 'Period Savings'; + } + if (periodSavingsUnitEl) { + periodSavingsUnitEl.textContent = `shown in ${adjective.toLowerCase()} equivalents`; } if (avgLabelEl) { avgLabelEl.textContent = `Avg ${adjective} Savings`; diff --git a/frontend/src/styles/charts.css b/frontend/src/styles/charts.css index 437fc4cd2..cd681218a 100644 --- a/frontend/src/styles/charts.css +++ b/frontend/src/styles/charts.css @@ -61,3 +61,9 @@ font-weight: var(--cudly-fw-bold); color: var(--cudly-primary); } + +.stat-unit-context { + font-size: 0.85rem; + color: var(--cudly-text-muted); + margin-top: 0.25rem; +}