diff --git a/lib/format.ts b/lib/format.ts index 2d3b171..9a4ed1c 100644 --- a/lib/format.ts +++ b/lib/format.ts @@ -14,11 +14,22 @@ export function formatTokens(n: number): string { /** * Format cost in dollars. - * null -> "$0.00*" (unknown model), 0.0073 -> "$0.0073", 1.5 -> "$1.50" + * null -> "$0.00*" (unknown model), 1.5 -> "$1.50", 100 -> "$100.00". + * + * Auto-promotes to 4 decimals when a positive fractional amount would otherwise + * round to "$0.00" at the default 2-decimal precision. Keeps displayed cost + * consistent across surfaces: a $0.0029 session reads as $0.0029 in both the + * overlay and the side panel, never $0.00 on one and $0.0029 on the other. + * Only activates when the caller accepts the default precision; explicit + * decimals arguments (e.g. decimals: 6) are respected as-is. + * * @param decimals - number of decimal places (default 2 for dashboard, use 4 for per-request overlay) */ export function formatCost(cost: number | null, decimals: number = 2): string { if (cost === null) return '$0.00*'; + if (decimals === 2 && cost > 0 && cost < 0.01) { + return `$${cost.toFixed(4)}`; + } return `$${cost.toFixed(decimals)}`; } diff --git a/tests/audit/format-audit.test.ts b/tests/audit/format-audit.test.ts index 0b74de4..986a951 100644 --- a/tests/audit/format-audit.test.ts +++ b/tests/audit/format-audit.test.ts @@ -57,6 +57,19 @@ describe('formatCost', () => { expect(formatCost(0.0073, 4)).toBe('$0.0073'); }); + test('fractional cost under $0.01 auto-promotes to 4 decimals at default', () => { + expect(formatCost(0.0029)).toBe('$0.0029'); + expect(formatCost(0.009)).toBe('$0.0090'); + }); + + test('fractional cost at $0.01 boundary stays 2 decimals', () => { + expect(formatCost(0.01)).toBe('$0.01'); + }); + + test('explicit decimals override wins over auto-promotion', () => { + expect(formatCost(0.0029, 4)).toBe('$0.0029'); + }); + test('large cost', () => { expect(formatCost(100.5)).toBe('$100.50'); }); diff --git a/ui/overlay-styles.ts b/ui/overlay-styles.ts index a8a8c24..9839255 100644 --- a/ui/overlay-styles.ts +++ b/ui/overlay-styles.ts @@ -253,7 +253,7 @@ export const OVERLAY_CSS = ` .lco-health-label--critical { color: #ef4444; } .lco-coaching { - font-size: 9px; + font-size: 10px; line-height: 1.4; color: var(--lco-muted); margin: 2px 0 3px; diff --git a/ui/overlay.ts b/ui/overlay.ts index 37eaee0..f1eba3c 100644 --- a/ui/overlay.ts +++ b/ui/overlay.ts @@ -391,8 +391,9 @@ export function createOverlay(): OverlayHandle { if (elSession && sessionVisible) { const { requestCount, totalInputTokens, totalOutputTokens, totalCost } = state.session; const total = totalInputTokens + totalOutputTokens; + const turnLabel = requestCount === 1 ? 'turn' : 'turns'; elSession.textContent = - `${requestCount} req · ~${fmt(total)} tok · ${fmtCost(totalCost)}`; + `${requestCount} ${turnLabel} · ~${fmt(total)} tok · ${fmtCost(totalCost)}`; } if (elHealth) {