diff --git a/desktop/frontend/src/components/StatusBar.tsx b/desktop/frontend/src/components/StatusBar.tsx
index 6cb0cb189..df339f5aa 100644
--- a/desktop/frontend/src/components/StatusBar.tsx
+++ b/desktop/frontend/src/components/StatusBar.tsx
@@ -3,6 +3,7 @@ import { Coins, Cpu, Wallet } from "lucide-react";
import { EffortSwitcher } from "./EffortSwitcher";
import { ModelSwitcher } from "./ModelSwitcher";
import { Tooltip } from "./Tooltip";
+import { UsageIndicator } from "./UsageIndicator";
import { SPINNER_WORDS, useI18n } from "../lib/i18n";
import type { BalanceInfo, ContextInfo, EffortInfo, JobView, Meta, Mode, WireUsage } from "../lib/types";
@@ -168,6 +169,12 @@ export function StatusBar({
{t("status.cacheAvg", { pct: avgPct })}
>
)}
+ {usage && (
+ <>
+ ·
+
+ >
+ )}
{jobs && jobs.length > 0 && (
<>
·
diff --git a/desktop/frontend/src/components/UsageIndicator.tsx b/desktop/frontend/src/components/UsageIndicator.tsx
new file mode 100644
index 000000000..53bde359b
--- /dev/null
+++ b/desktop/frontend/src/components/UsageIndicator.tsx
@@ -0,0 +1,48 @@
+import type { WireUsage } from "../lib/types";
+
+// UsageIndicator is a compact, info-dense readout of the latest turn's
+// token usage. It mounts in the status bar to the right of the cache-
+// hit % indicator. The three numbers are styled with a small caption
+// + value pair so the user can tell at a glance "this turn cost 4.2k
+// completion tokens, mostly from the prompt cache".
+//
+// We use a pill instead of a sparkline because the controller exposes
+// only the latest WireUsage payload (not a per-turn history). A
+// sparkline would need a separate ring buffer in the controller; a
+// future PR can add that and swap the pill for a real line chart.
+//
+// The component is intentionally tiny: 60 lines, no state, no
+// effects. It re-renders when the WireUsage prop changes (the
+// controller dispatches on every usage event during the turn).
+export function UsageIndicator({ usage }: { usage?: WireUsage }) {
+ if (!usage) return null;
+ // "k" rounding: 0–999 shows as the raw number, ≥1000 as "1.2k".
+ // Two significant digits feel right for both 800 (informative) and
+ // 184,000 (don't drown the status bar).
+ const fmt = (n: number): string => {
+ if (n < 1000) return String(n);
+ return `${(n / 1000).toFixed(n < 10_000 ? 1 : 0)}k`;
+ };
+ // The "saved" caption shows the cache-hit count as a fraction of
+ // the prompt tokens; deepSeek and Anthropic charge less for cache
+ // hits, so this number is the user-facing ROI.
+ const promptSaved = usage.cacheHitTokens;
+ return (
+
+
+ ↑
+ {fmt(usage.promptTokens)}
+
+
+ ↓
+ {fmt(usage.completionTokens)}
+
+ {promptSaved > 0 && (
+
+ ↻
+ {fmt(promptSaved)}
+
+ )}
+
+ );
+}
diff --git a/desktop/frontend/src/styles.css b/desktop/frontend/src/styles.css
index 90878fb0e..80b3a76da 100644
--- a/desktop/frontend/src/styles.css
+++ b/desktop/frontend/src/styles.css
@@ -5541,6 +5541,7 @@ body {
color: var(--fg);
background: var(--hover);
}
+
.composer__pasted {
display: flex;
flex-direction: column;
@@ -5881,4 +5882,47 @@ body {
.onboarding__skip:disabled {
opacity: 0.4;
cursor: not-allowed;
+
+
+/* UsageIndicator: a small three-cell pill that mounts in the status
+ bar. The up/down arrows are in unicode (cheaper than lucide icons
+ for an always-on status bar element). The cache-hit cell is
+ dimmer than the prompt/completion cells so the eye lands on the
+ billable numbers first. */
+.usage {
+ display: inline-flex;
+ align-items: center;
+ gap: 0;
+ margin-left: 6px;
+ padding: 2px 4px;
+ font-family: var(--mono);
+ font-size: 10.5px;
+ color: var(--fg-dim);
+ background: var(--bg-soft);
+ border: 1px solid var(--border-soft);
+ border-radius: 4px;
+}
+.usage__cell {
+ display: inline-flex;
+ align-items: baseline;
+ gap: 2px;
+ padding: 0 5px;
+ border-right: 1px solid var(--border-soft);
+}
+.usage__cell:last-child {
+ border-right: 0;
+}
+.usage__lbl {
+ opacity: 0.6;
+ font-size: 9.5px;
+}
+.usage__val {
+ color: var(--fg);
+ font-variant-numeric: tabular-nums;
+}
+.usage__cell--saved {
+ color: var(--add-fg);
+}
+.usage__cell--saved .usage__val {
+ color: var(--add-fg);
}