diff --git a/apps/web/src/pages/mission-control-page.tsx b/apps/web/src/pages/mission-control-page.tsx
index a1a0b60f0..9d1567439 100644
--- a/apps/web/src/pages/mission-control-page.tsx
+++ b/apps/web/src/pages/mission-control-page.tsx
@@ -249,6 +249,7 @@ export function MissionControlRoute({
const days =
insightsActivity.kind === "ready" ? insightsActivity.data.days : [];
+ const insightsReady = insightsActivity.kind === "ready";
const todayKey = new Date().toISOString().slice(0, 10);
const today = days.find((day) => day.day === todayKey) ?? null;
const priorDays = days.filter((day) => day.day !== todayKey);
@@ -256,9 +257,13 @@ export function MissionControlRoute({
priorDays.length > 0
? priorDays.reduce((sum, day) => sum + day.turns, 0) / priorDays.length
: null;
- const todaySpend =
- today === null
- ? null
+ // Ready but no today row means zero activity today, not "unknown". Dash
+ // only while the activity query is still settling (or cost rates unknown).
+ const runsToday = insightsReady ? (today?.turns ?? 0) : null;
+ const todaySpend = !insightsReady
+ ? null
+ : today === null
+ ? 0
: today.byModel.some((model) => model.costUsd === null)
? null
: today.byModel.reduce((sum, model) => sum + (model.costUsd ?? 0), 0);
@@ -302,15 +307,17 @@ export function MissionControlRoute({
/>
diff --git a/apps/web/test/mission-control-page.test.tsx b/apps/web/test/mission-control-page.test.tsx
index 34162776a..1eda77146 100644
--- a/apps/web/test/mission-control-page.test.tsx
+++ b/apps/web/test/mission-control-page.test.tsx
@@ -236,6 +236,76 @@ describe("MissionControlRoute", () => {
expect(container.textContent).toContain("Nothing recent yet.");
});
+ test("ready activity with no today row shows 0 / $0.00, not double em-dash (CL-6798)", async () => {
+ stubEmptyBenchFetch();
+ container = document.createElement("div");
+ document.body.appendChild(container);
+ root = createRoot(container);
+ await act(async () => {
+ root?.render(
+
+ undefined}>
+
+ undefined} />
+
+
+ ,
+ );
+ });
+ for (let count = 0; count < 5; count += 1) {
+ await act(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 0));
+ });
+ }
+ const text = container.textContent ?? "";
+ // Ready + missing today is zero, not unknown — one empty treatment, not
+ // value em-dash stacked on sub em-dash.
+ expect(text).toContain("Runs today");
+ expect(text).not.toMatch(/Runs today—/);
+ expect(text).toMatch(/Runs today0/);
+ expect(text).toContain("Spend today");
+ expect(text).toContain("$0.00");
+ expect(text).not.toContain("7-day activity");
+ expect(text).toContain("so far today");
+ });
+
+ test("Spend today keeps em-dash when today's cost rate is unknown (CL-6798)", async () => {
+ const todayKey = new Date().toISOString().slice(0, 10);
+ stubBenchFetchWithActivity([
+ {
+ day: todayKey,
+ turns: 3,
+ tokens: 50,
+ byModel: [{ model: "m", tokens: 50, costUsd: null }],
+ },
+ ]);
+ container = document.createElement("div");
+ document.body.appendChild(container);
+ root = createRoot(container);
+ await act(async () => {
+ root?.render(
+
+ undefined}>
+
+ undefined} />
+
+
+ ,
+ );
+ });
+ for (let count = 0; count < 5; count += 1) {
+ await act(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 0));
+ });
+ }
+ const text = container.textContent ?? "";
+ expect(text).toMatch(/Runs today3/);
+ expect(text).toContain("so far today");
+ expect(text).not.toContain("7-day activity");
+ // Cost unknown → em-dash for spend value; subcopy still names today.
+ expect(text).toMatch(/Spend today—so far today/);
+ });
+
test("This week's run count includes today, so it is never less than runs today (CL-6667)", async () => {
const todayKey = new Date().toISOString().slice(0, 10);
const yesterdayKey = new Date(Date.now() - 24 * 60 * 60 * 1000)