From b93bd439b89d1173cf797ca417fce3331522969a Mon Sep 17 00:00:00 2001 From: Jean du Plessis Date: Fri, 31 Jul 2026 15:49:56 +0200 Subject: [PATCH 1/2] fix(cost-insights): refine spend chart states --- .../overview/SpendEvidenceCard.tsx | 29 ++-- .../settings/CostInsightsSettingsView.tsx | 128 +++++++++--------- 2 files changed, 74 insertions(+), 83 deletions(-) diff --git a/apps/web/src/components/cost-insights/overview/SpendEvidenceCard.tsx b/apps/web/src/components/cost-insights/overview/SpendEvidenceCard.tsx index 322e20443d..8a0250d669 100644 --- a/apps/web/src/components/cost-insights/overview/SpendEvidenceCard.tsx +++ b/apps/web/src/components/cost-insights/overview/SpendEvidenceCard.tsx @@ -36,8 +36,7 @@ export function SpendEvidenceCard({ const totals = evidence.map(point => point.variableUsd + point.scheduledUsd); const axisMax = niceCeil(Math.max(0, ...totals)); const hasSpend = axisMax > 0; - const hasIncompleteEvidence = evidence.some(point => point.coverage !== 'complete'); - const shouldRenderChart = hasSpend || hasIncompleteEvidence; + const hasIncompleteEvidence = hasSpend && evidence.some(point => point.coverage !== 'complete'); const rangeLabel = { '1h': 'This hour', '24h': 'Last 24 hours', @@ -46,7 +45,7 @@ export function SpendEvidenceCard({ '90d': 'Last 90 days', }[range]; const highest = evidence - .filter(point => point.coverage !== 'unavailable') + .filter(point => point.variableUsd + point.scheduledUsd > 0) .reduce<(typeof evidence)[number] | undefined>((currentHighest, point) => { if (!currentHighest) return point; const currentTotal = currentHighest.variableUsd + currentHighest.scheduledUsd; @@ -112,7 +111,7 @@ export function SpendEvidenceCard({ Use Left and Right Arrow keys to inspect each period. Use Home and End to jump to the first or last period.

- {shouldRenderChart ? ( + {hasSpend ? (
{rangeLabel} spend by period -
- {!data.enabled && ( -

- Spend Alerts are off. Saved anomaly and threshold settings apply when you turn - Spend Alerts on again. -

- )} -
-
-

- Spend anomalies -

-

- Compare current-hour usage-based spend with your recent hourly pattern. -

-
-
- - - onChange?.({ anomalyAlertsEnabled })} - /> -
-
+ {data.enabled && ( +
+
+
+

+ Spend anomalies +

+

+ Compare current-hour usage-based spend with your recent hourly pattern. +

+
+
+ + + onChange?.({ anomalyAlertsEnabled })} + /> +
+
- onChange?.({ thresholdUsd })} - /> + onChange?.({ thresholdUsd })} + /> - onChange?.({ threshold7DayUsd })} - /> + onChange?.({ threshold7DayUsd })} + /> - onChange?.({ threshold30DayUsd })} - /> -
+ onChange?.({ threshold30DayUsd })} + /> +
+ )}
From e1103a5dc8b26084c6200d286f02900c05a02726 Mon Sep 17 00:00:00 2001 From: Jean du Plessis Date: Fri, 31 Jul 2026 16:10:02 +0200 Subject: [PATCH 2/2] fix(cost-insights): preserve hidden state semantics --- .../CostInsightsOverviewClient.tsx | 6 +++- .../CostInsightsSettingsClient.tsx | 21 +++++++----- .../overview/SpendEvidenceCard.tsx | 33 ++++++++++--------- .../settings/CostInsightsSettingsView.tsx | 10 +++++- 4 files changed, 45 insertions(+), 25 deletions(-) diff --git a/apps/web/src/components/cost-insights/CostInsightsOverviewClient.tsx b/apps/web/src/components/cost-insights/CostInsightsOverviewClient.tsx index 77f5b7c4c6..b2127b4c55 100644 --- a/apps/web/src/components/cost-insights/CostInsightsOverviewClient.tsx +++ b/apps/web/src/components/cost-insights/CostInsightsOverviewClient.tsx @@ -159,7 +159,11 @@ export function CostInsightsOverviewClient({ : alert.type === 'threshold_7d' ? 'spend-threshold-7d' : 'spend-threshold-24h'; - router.push(`${basePath}/config#${thresholdAnchor}`); + router.push( + dashboard?.enabled + ? `${basePath}/config#${thresholdAnchor}` + : `${basePath}/config#spend-alert-options-hidden` + ); return; } diff --git a/apps/web/src/components/cost-insights/CostInsightsSettingsClient.tsx b/apps/web/src/components/cost-insights/CostInsightsSettingsClient.tsx index e0e8dc9cbb..38a34f8ba7 100644 --- a/apps/web/src/components/cost-insights/CostInsightsSettingsClient.tsx +++ b/apps/web/src/components/cost-insights/CostInsightsSettingsClient.tsx @@ -235,9 +235,11 @@ function CostInsightsSettingsForm({ threshold7DayUsd: validateThresholdUsd(form.threshold7DayUsd), threshold30DayUsd: validateThresholdUsd(form.threshold30DayUsd), }; - const hasValidationError = Boolean( - validations.thresholdUsd || validations.threshold7DayUsd || validations.threshold30DayUsd - ); + const hasValidationError = + form.enabled && + Boolean( + validations.thresholdUsd || validations.threshold7DayUsd || validations.threshold30DayUsd + ); const dirty = form.enabled !== settings.enabled || form.anomalyAlertsEnabled !== settings.anomalyAlertsEnabled || @@ -266,15 +268,18 @@ function CostInsightsSettingsForm({ const handleSave = () => { if (!dirty || hasValidationError || settings.readOnly) return; + const thresholdUsd = form.enabled ? form.thresholdUsd : settings.thresholdUsd; + const threshold7DayUsd = form.enabled + ? form.threshold7DayUsd + : (settings.threshold7DayUsd ?? ''); + const threshold30DayUsd = form.enabled ? form.threshold30DayUsd : settings.threshold30DayUsd; onSave({ spendAlertsEnabled: form.enabled, anomalyAlertsEnabled: form.anomalyAlertsEnabled, costSuggestionsEnabled: form.suggestionsEnabled, - spendThresholdUsd: form.thresholdUsd.trim() === '' ? null : form.thresholdUsd.trim(), - spend7DayThresholdUsd: - form.threshold7DayUsd.trim() === '' ? null : form.threshold7DayUsd.trim(), - spend30DayThresholdUsd: - form.threshold30DayUsd.trim() === '' ? null : form.threshold30DayUsd.trim(), + spendThresholdUsd: thresholdUsd.trim() === '' ? null : thresholdUsd.trim(), + spend7DayThresholdUsd: threshold7DayUsd.trim() === '' ? null : threshold7DayUsd.trim(), + spend30DayThresholdUsd: threshold30DayUsd.trim() === '' ? null : threshold30DayUsd.trim(), }); }; diff --git a/apps/web/src/components/cost-insights/overview/SpendEvidenceCard.tsx b/apps/web/src/components/cost-insights/overview/SpendEvidenceCard.tsx index 8a0250d669..fa7ed094e3 100644 --- a/apps/web/src/components/cost-insights/overview/SpendEvidenceCard.tsx +++ b/apps/web/src/components/cost-insights/overview/SpendEvidenceCard.tsx @@ -36,7 +36,8 @@ export function SpendEvidenceCard({ const totals = evidence.map(point => point.variableUsd + point.scheduledUsd); const axisMax = niceCeil(Math.max(0, ...totals)); const hasSpend = axisMax > 0; - const hasIncompleteEvidence = hasSpend && evidence.some(point => point.coverage !== 'complete'); + const hasIncompleteEvidence = evidence.some(point => point.coverage !== 'complete'); + const shouldRenderChart = hasSpend || hasIncompleteEvidence; const rangeLabel = { '1h': 'This hour', '24h': 'Last 24 hours', @@ -45,7 +46,7 @@ export function SpendEvidenceCard({ '90d': 'Last 90 days', }[range]; const highest = evidence - .filter(point => point.variableUsd + point.scheduledUsd > 0) + .filter(point => point.coverage !== 'unavailable' && point.variableUsd + point.scheduledUsd > 0) .reduce<(typeof evidence)[number] | undefined>((currentHighest, point) => { if (!currentHighest) return point; const currentTotal = currentHighest.variableUsd + currentHighest.scheduledUsd; @@ -111,7 +112,7 @@ export function SpendEvidenceCard({ Use Left and Right Arrow keys to inspect each period. Use Home and End to jump to the first or last period.

- {hasSpend ? ( + {shouldRenderChart ? (
@@ -193,9 +196,14 @@ export function SpendEvidenceCard({
{point.label}
- {pointTotal === 0 ? ( + {point.coverage === 'unavailable' ? ( +

+ Spend data unavailable. Covered {point.coveredHours} of{' '} + {point.totalHours} hours. +

+ ) : point.coverage === 'complete' && pointTotal === 0 ? (

No spend.

- ) : point.coverage !== 'unavailable' ? ( + ) : (
{point.coverage === 'partial' ? 'Known spend' : 'Total'} @@ -223,11 +231,6 @@ export function SpendEvidenceCard({
)}
- ) : ( -

- Spend data unavailable. Covered {point.coveredHours} of{' '} - {point.totalHours} hours. -

)}
diff --git a/apps/web/src/components/cost-insights/settings/CostInsightsSettingsView.tsx b/apps/web/src/components/cost-insights/settings/CostInsightsSettingsView.tsx index d971bf7035..79b4ea1280 100644 --- a/apps/web/src/components/cost-insights/settings/CostInsightsSettingsView.tsx +++ b/apps/web/src/components/cost-insights/settings/CostInsightsSettingsView.tsx @@ -108,7 +108,7 @@ export function CostInsightsSettingsView({
- {data.enabled && ( + {data.enabled ? (
onChange?.({ threshold30DayUsd })} />
+ ) : ( +

+ Turn on Spend Alerts to manage anomaly and threshold settings. +

)}