## Demo
_No behavior change — this is a frontend-only DRY refactor, so the proof is verification-of-no-regression: the two gauges still render identically and the dead screen/route is gone._
UI — verify both gauges render unchanged (no regression)
1. Open the app and navigate to the AWS Spend dashboard (/aws-spend).
2. On the executive metric cards, locate the "Total AWS Spend" (QTD) card and confirm its Budget Pace Gauge renders as before: the % of QTD Budget label on the left, the variance $ amount on the right, the green→yellow→red gradient track (yellow stop at 50%), the marker, and the 0% / 100% / 150% scale labels.
3. Locate the "Projected EOQ Spend" card and confirm its Budget Variance Gauge renders as before: the % of Budget label on the left, the status word (On Track / At Risk / Over Budget / Critical) on the right, the gradient track (yellow stop at 67%), the marker, and the same 0% / 100% / 150% scale labels.
4. Confirm both gauges are pixel-identical to main — same colors, gradient stops, marker positions, label text, and scale labels.
5. Confirm the dead legacy screen is gone: the /aws-spend route still loads (it lazy-loads AWSSpendShell directly, not the deleted index.tsx), and there is no second/legacy AWS Spend screen or Data Lineage modal (lineage is shown via the live side panel AWSDataLineageContent.tsx).
> _Screenshot: the AWS Spend dashboard executive cards showing the Pace gauge (Total AWS Spend) and Variance gauge (Projected EOQ Spend) rendering identically to main — SCREENSHOT_PENDING_ <!-- paste screenshot here -->
<img width="2624" height="1636" alt="image" src="https://github.com/user-attachments/assets/85d84141-2932-4975-9c2c-ea1fa02a32a5" />
Supporting proof — scoped tests + typecheck green
Ran the 3 changed gauge spec files directly (pnpm vitest run on the touched files only):
✓ src/screens/AWSSpend/components/BudgetVarianceGauge.spec.tsx (2 tests)✓ src/screens/AWSSpend/components/BudgetPaceGauge.spec.tsx (2 tests)
✓ src/screens/AWSSpend/components/BudgetGauge.spec.tsx (6 tests)
Test Files 3 passed (3)
Tests 10 passed (10)
pnpm tsc --noEmit exits 0 with no errors.
Most at risk from this change: (1) the two wrappers' public prop contracts that AWSSpendShell.tsx depends on, and (2) the deleted index.tsx accidentally taking the live /aws-spend route with it. Both checked and held:
# AWSSpendShell.tsx still imports + renders both wrappers (render sites untouched)klair-client/src/screens/AWSSpend/AWSSpendShell.tsx:41:import BudgetVarianceGauge from './components/BudgetVarianceGauge';
klair-client/src/screens/AWSSpend/AWSSpendShell.tsx:42:import BudgetPaceGauge from './components/BudgetPaceGauge';
klair-client/src/screens/AWSSpend/AWSSpendShell.tsx:872: <BudgetPaceGauge ... />
klair-client/src/screens/AWSSpend/AWSSpendShell.tsx:929: <BudgetVarianceGauge ... />
# Route lazy-loads the shell explicitly — not the deleted index.tsx
src/shells/DesktopShell/routes.tsx:170:const AWSSpendShell = lazy(() => import('@/screens/AWSSpend/AWSSpendShell'));
# Zero dangling references to any deleted module (index.tsx / DataLineageModal / dashboardUtils)
(grep returned no matches)
This confirms the refactor is behavior-preserving: the shell render sites compile unchanged, the route is intact, and nothing references the removed dead code.
## Summary
The AWS Spend dashboard renders two near-identical gradient gauges as trend indicators on its executive metric cards: BudgetPaceGauge (on the "Total AWS Spend" QTD card) and BudgetVarianceGauge (on the "Projected EOQ Spend" card). Both hand-rolled the same presentational markup — an outer wrapper, a header row with a big percentage label on the left and a small status/variance label on the right, a h-1.5 green→yellow→red gradient track with an absolutely-positioned marker, and a 0% / 100% / 150% scale-label row mapping a 0–150% domain onto a 0–100% pixel range. This PR extracts that duplicated markup into a single new presentational BudgetGauge component and reduces both gauges to thin wrappers, then deletes the dead legacy V1 screen and its now-orphaned modules. It is a frontend-only DRY refactor with no user-visible behavior change.
Linear ticket: [KLAIR-1565 — [AWS Spend] Refactor the two gauges into one so that its DRY](https://linear.app/builder-team/issue/KLAIR-1565/aws-spend-refactor-the-two-gauges-into-one-so-that-its-dry)
## Specs
- [46-unify-budget-gauge](features/aws-spend/aws-spend-dashboard/specs/46-unify-budget-gauge/spec.md) — Extract the shared gauge presentational layer into one BudgetGauge component (header row, h-1.5 gradient track, position marker, 0/100/150 scale labels on a 0–150→0–100% mapping); reduce BudgetPaceGauge and BudgetVarianceGauge to thin wrappers (public prop contracts unchanged), each supplying its differing inputs (status computation, gradient yellow stop, right-label content); and delete the dead legacy V1 screen and its now-orphaned modules.
## Implementation
New shared component
- klair-client/src/screens/AWSSpend/components/BudgetGauge.tsx — purely presentational (no status/pace/variance math): outer wrapper, header row, h-1.5 gradient bar, position marker, and 0 / 100 / 150 scale labels. Props: leftLabel, rightLabel, textColor, markerPosition, gradientYellowStop. Defensively clamps markerPosition to [0, 100]; derives the centered 100%-label position from the shared scale constants (~66.67%).
Two slimmed wrappers (public APIs unchanged)
- BudgetPaceGauge.tsx — keeps its { actualSpend, budgetedQTD, formatCurrency } contract and default export; retains all pace math (getStatus/getStatusConfig, useMemo for variance/config/markerPosition/pacePercent, the budgetedQTD <= 0 edge case, variance-display formatting); renders <BudgetGauge gradientYellowStop={50} />.
- BudgetVarianceGauge.tsx — keeps its { variancePercent, thresholds? } contract and default export; retains the four-zone status logic, budgetConsumedPercent derivation, clamp, and marker calc; renders <BudgetGauge gradientYellowStop={67} />.
- AWSSpendShell.tsx (the sole live render site) is untouched — the wrappers preserve their props/exports, so the shell compiles with zero call-site churn.
Dead code removed (full clean removal — grep-verified zero resolving imports before each deletion)
- klair-client/src/screens/AWSSpend/index.tsx — dead/legacy V1 screen, superseded by AWSSpendShell.tsx, reachable by no route (the router lazy-loads @/screens/AWSSpend/AWSSpendShell explicitly).
- klair-client/src/screens/AWSSpend/components/DataLineageModal.tsx — imported only by index.tsx; its lineage UI is already preserved live by AWSDataLineageContent.tsx (side panel).
- klair-client/src/screens/AWSSpend/utils/dashboardUtils.ts — only production consumer was index.tsx (the shell uses its own local formatCurrency).
- klair-client/src/screens/AWSSpend/index.spec.ts — only exercised dashboardUtils; covers no live code path once the util is gone.
pnpm tsc --noEmit and eslint --max-warnings 0 both pass.
## Test coverage (10 tests, all passing)
- BudgetGauge.spec.tsx (6): label render, textColor applied to both spans, gradient stop, marker position, and both marker-clamp branches.
- BudgetPaceGauge.spec.tsx (2): 50% gradient stop label + variance string; budgetedQTD = 0 edge case.
- BudgetVarianceGauge.spec.tsx (2): On Track + Critical statuses across the four zones.
## Self-review
No issues found. Output fidelity verified byte-for-byte against origin/main for both gauges; the dead-code deletion is safe (zero dangling references); lint and types are clean.
## No behavior change
Pixel-identical gauge output — same markup, colors, gradient stops, marker positions, label text, scale labels, and edge-case behavior for every input. The only live render site, AWSSpendShell.tsx, was left untouched, so both the QTD "Total AWS Spend" pace gauge and the "Projected EOQ Spend" variance gauge render exactly as before.
🤖 Generated with [Claude Code](https://claude.com/claude-code)