Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ All notable changes to `omnidash` are documented here.

This project follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## v1.1.8 (2026-10-09)

### Changes
- The Overview titles three cards as the demo mockup does: Actual spend (was Spend), Agent calls (was
Measured runs) and Tokens processed (was Tokens in and out). The figures behind them are unchanged
(OMN-20758)

## v1.1.7 (2026-10-08)

### Fixed
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "omnidash",
"version": "1.1.7",
"version": "1.1.8",
"description": "Composable widget dashboard for real-time observability — Vite + React with per-widget 2D and 3D variants.",
"main": "index.js",
"homepage": "https://github.com/OmniNode-ai/omnidash#readme",
Expand Down
10 changes: 5 additions & 5 deletions src/pages/LocalDashboardPage.live.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ describe('LocalDashboardPage widget states (F26, F27)', () => {
harness.reachable = new Set([DECISIONS, SAVINGS]);
render(<LocalDashboardPage pageName="overview" />);
await settle();
const spend = screen.getByRole('heading', { name: 'Spend' }).closest('article')!;
const spend = screen.getByRole('heading', { name: 'Actual spend' }).closest('article')!;
expect(within(spend).getByText(`Not served: ${METERING}`)).toBeInTheDocument();
const lastRun = screen.getByRole('heading', { name: 'Last run' }).closest('article')!;
expect(within(lastRun).getByText('run-1')).toBeInTheDocument();
Expand Down Expand Up @@ -151,7 +151,7 @@ describe('LocalDashboardPage widget states (F26, F27)', () => {
render(<LocalDashboardPage pageName="overview" />);
await settle();

for (const title of ['Spend', 'Savings', 'Measured runs', 'Tokens in and out']) {
for (const title of ['Actual spend', 'Savings', 'Agent calls', 'Tokens processed']) {
const panel = screen.getByRole('heading', { name: title }).closest('article')!;
expect(within(panel).getAllByRole('alert'), title).toHaveLength(1);
}
Expand All @@ -168,7 +168,7 @@ describe('LocalDashboardPage widget states (F26, F27)', () => {
: Promise.resolve(topic === DECISIONS ? [decisionRow('run-1')] : [{ sessions: [] }]);
render(<LocalDashboardPage pageName="overview" />);
await settle();
const spend = screen.getByRole('heading', { name: 'Spend' }).closest('article')!;
const spend = screen.getByRole('heading', { name: 'Actual spend' }).closest('article')!;
expect(within(spend).getByRole('alert')).toHaveTextContent(`${METERING}: HTTP 503`);
});

Expand All @@ -177,7 +177,7 @@ describe('LocalDashboardPage widget states (F26, F27)', () => {
harness.answer = (topic) => topic === METERING ? new Promise(() => {}) : Promise.resolve(topic === DECISIONS ? [decisionRow('run-1')] : [{ sessions: [] }]);
render(<LocalDashboardPage pageName="overview" />);
await act(async () => { await vi.advanceTimersByTimeAsync(5_000); });
const spend = screen.getByRole('heading', { name: 'Spend' }).closest('article')!;
const spend = screen.getByRole('heading', { name: 'Actual spend' }).closest('article')!;
expect(within(spend).getByRole('alert')).toHaveTextContent(`${METERING}: no answer in 5 s`);
});

Expand All @@ -189,7 +189,7 @@ describe('LocalDashboardPage widget states (F26, F27)', () => {
? Promise.reject(new Error(`Projection ${METERING} failed: HTTP 503 Service Unavailable`))
: Promise.resolve(topic === DECISIONS ? [decisionRow('run-1')] : [{ sessions: [] }]);
await act(async () => { await vi.advanceTimersByTimeAsync(30_000); });
const spend = screen.getByRole('heading', { name: 'Spend' }).closest('article')!;
const spend = screen.getByRole('heading', { name: 'Actual spend' }).closest('article')!;
expect(within(spend).getByText('$0.0069')).toBeInTheDocument();
expect(within(spend).getByRole('alert')).toHaveTextContent(/HTTP 503 Service Unavailable; last good 30s ago/);
});
Expand Down
32 changes: 16 additions & 16 deletions src/pages/LocalDashboardPage.omn19980.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ describe('OMN-19980 AC2c: a savings card with nothing measured or served says so
const card = panel('Savings');
expect(within(card).getByText('Baseline unresolved')).toBeInTheDocument();
noFigure(card);
expect(within(panel('Spend')).getByText('$0.0100')).toBeInTheDocument();
expect(within(panel('Actual spend')).getByText('$0.0100')).toBeInTheDocument();
expect(within(panel('Recent runs')).getByText(LOCAL_COST)).toBeInTheDocument();
});
});
Expand Down Expand Up @@ -239,8 +239,8 @@ describe('OMN-19980 Step B: the Overview Savings total is metering-summary.v1\'s
expect(within(savings).queryByText(/claude-opus-4-6/)).toBeNull();
expect(within(savings).queryByText('$0.5000')).toBeNull();
// Amendment 2: Spend and Measured runs read the same all row now, not cost.savings-overview.v1 (0.01 and 4).
expect(within(panel('Spend')).getByText('$0.0071')).toBeInTheDocument();
expect(within(panel('Measured runs')).getByText('7')).toBeInTheDocument();
expect(within(panel('Actual spend')).getByText('$0.0071')).toBeInTheDocument();
expect(within(panel('Agent calls')).getByText('7')).toBeInTheDocument();
});

it('T2: metering-summary.v1 not in the census: the card names it and shows no figure', async () => {
Expand All @@ -257,7 +257,7 @@ describe('OMN-19980 Step B: the Overview Savings total is metering-summary.v1\'s
const savings = panel('Savings');
expect(within(savings).getByText('Baseline unresolved')).toBeInTheDocument();
noFigure(savings);
expect(within(panel('Spend')).getByText('$0.0071')).toBeInTheDocument();
expect(within(panel('Actual spend')).getByText('$0.0071')).toBeInTheDocument();
expect(within(panel('Recent runs')).getByText(LOCAL_COST)).toBeInTheDocument();
});

Expand Down Expand Up @@ -303,7 +303,7 @@ describe('OMN-19980 Step B: the Overview Savings total is metering-summary.v1\'s
});

describe('OMN-19980 Step B (Amendment 2): Spend, Measured runs and Tokens read the same all row as Savings', () => {
const HEADLINES = ['Spend', 'Measured runs', 'Tokens in and out'] as const;
const HEADLINES = ['Actual spend', 'Agent calls', 'Tokens processed'] as const;
/** The card's figure and caption lines, exactly (the panel's read-state line is not the card's figure). */
const figure = (title: string) => [...panel(title).querySelectorAll('.local-dashboard-metric')].map((node) => node.textContent);
const captions = (title: string) => [...panel(title).querySelectorAll('.local-dashboard-caption')].map((node) => node.textContent);
Expand All @@ -314,16 +314,16 @@ describe('OMN-19980 Step B (Amendment 2): Spend, Measured runs and Tokens read t
meteringRow({}),
] });
await open('overview');
const spend = panel('Spend');
const spend = panel('Actual spend');
expect(within(spend).getByText('$0.0071')).toBeInTheDocument();
expect(within(spend).queryByText('$0.0100')).toBeNull();
expect(within(spend).queryByText('$0.0022')).toBeNull();
const measured = panel('Measured runs');
const measured = panel('Agent calls');
expect(within(measured).getByText('7')).toBeInTheDocument();
expect(within(measured).getByText('Of 9 runs · Unknown-token runs excluded: 1 · Unknown-spend runs excluded: 1')).toBeInTheDocument();
expect(within(measured).queryByText('4')).toBeNull();
expect(within(measured).queryByText(/Estimated runs excluded/)).toBeNull();
const tokens = panel('Tokens in and out');
const tokens = panel('Tokens processed');
expect(within(tokens).getByText('1,620 in · 520 out')).toBeInTheDocument();
expect(within(tokens).queryByText(/Not served yet/)).toBeNull();
expect(within(tokens).queryByText(/111/)).toBeNull();
Expand All @@ -343,9 +343,9 @@ describe('OMN-19980 Step B (Amendment 2): Spend, Measured runs and Tokens read t
served({ [METERING]: [meteringRow({ baseline_state: 'unresolved', pricing_manifest_version: null, counterfactual_usd: null, savings_usd: null })] });
await open('overview');
expect(within(panel('Savings')).getByText('Baseline unresolved')).toBeInTheDocument();
expect(within(panel('Spend')).getByText('$0.0071')).toBeInTheDocument();
expect(within(panel('Measured runs')).getByText('7')).toBeInTheDocument();
expect(within(panel('Tokens in and out')).getByText('1,620 in · 520 out')).toBeInTheDocument();
expect(within(panel('Actual spend')).getByText('$0.0071')).toBeInTheDocument();
expect(within(panel('Agent calls')).getByText('7')).toBeInTheDocument();
expect(within(panel('Tokens processed')).getByText('1,620 in · 520 out')).toBeInTheDocument();
for (const title of HEADLINES) expect(within(panel(title)).queryByText('Baseline unresolved'), title).toBeNull();
});

Expand All @@ -357,7 +357,7 @@ describe('OMN-19980 Step B (Amendment 2): Spend, Measured runs and Tokens read t
await open('overview');
for (const title of HEADLINES) expect(figure(title), title).toEqual(['Not measured']);
// The run counts that are served still say why nothing was measured; they are counts, not the figure.
expect(captions('Measured runs')).toEqual(['Of 4 runs · Unknown-token runs excluded: 4 · Unknown-spend runs excluded: 0']);
expect(captions('Agent calls')).toEqual(['Of 4 runs · Unknown-token runs excluded: 4 · Unknown-spend runs excluded: 0']);
});

it('M5: after a baseline change every card reads the all row refreshed last, the same row as Savings', async () => {
Expand All @@ -368,11 +368,11 @@ describe('OMN-19980 Step B (Amendment 2): Spend, Measured runs and Tokens read t
] });
await open('overview');
expect(within(panel('Savings')).getByText('$1.2252')).toBeInTheDocument();
expect(within(panel('Spend')).getByText('$0.0071')).toBeInTheDocument();
expect(within(panel('Measured runs')).getByText('7')).toBeInTheDocument();
expect(within(panel('Tokens in and out')).getByText('1,620 in · 520 out')).toBeInTheDocument();
expect(within(panel('Actual spend')).getByText('$0.0071')).toBeInTheDocument();
expect(within(panel('Agent calls')).getByText('7')).toBeInTheDocument();
expect(within(panel('Tokens processed')).getByText('1,620 in · 520 out')).toBeInTheDocument();
expect([...HEADLINES].map(figure)).toEqual([['$0.0071'], ['7'], ['1,620 in · 520 out']]);
expect(captions('Measured runs')).toEqual(['Of 9 runs · Unknown-token runs excluded: 1 · Unknown-spend runs excluded: 1']);
expect(captions('Agent calls')).toEqual(['Of 9 runs · Unknown-token runs excluded: 1 · Unknown-spend runs excluded: 1']);
});

it('M6: two all rows refreshed at the same moment are refused by name on every card, never one picked', async () => {
Expand Down
94 changes: 94 additions & 0 deletions src/pages/LocalDashboardPage.omn20758.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// @vitest-environment jsdom
// OMN-20758: the Overview titles three metrics as the demo mockup does -- Actual spend, Agent calls, Tokens processed --
// over the same served figures the cards showed as Spend, Measured runs and Tokens in and out.
//
// Failure modes, each with a test below:
// T1 a renamed card is missing, or shows a different figure than the served row's field it always showed
// (spend_usd, runs_measured, tokens_in and tokens_out of metering-summary.v1's all-time row);
// T2 any of the three old titles still renders on the Overview;
// T3 a renamed card keeps its own caption (Agent calls keeps the run-class line), so only the title moved.
import { act, render, screen, within } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

const METERING = 'onex.snapshot.projection.metering-summary.v1';

const harness = vi.hoisted(() => ({
reachable: new Set<string>(),
answer: (_topic: string): Promise<unknown[]> => Promise.resolve([]),
}));

vi.mock('@/data-source', () => ({
createSnapshotSource: () => ({
async *readAll() { yield []; },
readSnapshot: async (topic: string) => {
const rows = await harness.answer(topic);
return { rows, rowCount: rows.length, dataFreshness: 'fresh', latestEventAt: null, readAt: new Date().toISOString() };
},
}),
}));
vi.mock('@/data-source/data-source-override', () => ({ resolveEffectiveDataSource: () => ({ mode: 'http' }) }));
vi.mock('@/data-source/exposure-census', () => ({
fetchExposureCensus: async () => ({
rows: [...harness.reachable].map((topic) => ({ topic, reachability: 'reachable' })),
}),
}));

import { LocalDashboardPage } from './LocalDashboardPage';

// An all-time row shaped like the 2026-10-09 re-walk's: 23 runs, tokens and a spend the cards must show as served.
const ALL_ROW = {
window_kind: 'all', window_start: '', as_of: '2026-10-09T12:00:00Z', baseline_model: 'claude-sonnet-5-5',
pricing_manifest_version: '1.0.0', baseline_state: 'resolved', runs_total: 23, runs_measured: 23,
runs_unknown_tokens: 0, runs_unknown_spend: 0, tokens_in: 5756, tokens_out: 378, spend_usd: 0.0123,
counterfactual_usd: 0.015292, savings_usd: 0.002992, savings_per_measured_run_usd: 0.00013,
};

async function overview(): Promise<void> {
harness.reachable = new Set([METERING]);
harness.answer = (topic) => Promise.resolve(topic === METERING ? [ALL_ROW] : []);
render(<LocalDashboardPage pageName="overview" />);
await act(async () => { await vi.advanceTimersByTimeAsync(0); });
}

function card(title: string): HTMLElement {
const article = screen.getByRole('heading', { name: title }).closest('article');
if (article === null) throw new Error(`no ${title} card on the Overview`);
return article as HTMLElement;
}

const figure = (title: string) => card(title).querySelector('.local-dashboard-metric')?.textContent ?? '';

describe('OMN-20758 Overview titles match the demo mockup', () => {
beforeEach(() => {
vi.useFakeTimers({ toFake: ['setInterval', 'clearInterval', 'setTimeout', 'clearTimeout', 'Date'] });
vi.setSystemTime(new Date('2026-10-09T12:05:00Z'));
});
afterEach(() => { vi.useRealTimers(); });

it('T1: Actual spend shows the served spend_usd', async () => {
await overview();
expect(figure('Actual spend')).toBe('$0.0123');
});

it('T1: Agent calls shows the served runs_measured', async () => {
await overview();
expect(figure('Agent calls')).toBe('23');
});

it('T1: Tokens processed shows the served tokens in and out', async () => {
await overview();
expect(figure('Tokens processed')).toBe('5,756 in · 378 out');
});

it('T2: none of the old titles renders', async () => {
await overview();
for (const old of ['Spend', 'Measured runs', 'Tokens in and out']) {
expect(screen.queryByRole('heading', { name: old })).toBeNull();
}
});

it('T3: Agent calls keeps its run-class caption', async () => {
await overview();
expect(within(card('Agent calls')).getByText(/^Of 23 runs/)).toBeInTheDocument();
});
});
6 changes: 3 additions & 3 deletions src/pages/local/overview.contracts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{
"component_id": "overview-spend",
"component_kind": "metric_card",
"title": "Spend",
"title": "Actual spend",
"contract_version": {
"major": 1,
"minor": 0,
Expand Down Expand Up @@ -60,7 +60,7 @@
{
"component_id": "overview-measured",
"component_kind": "metric_card",
"title": "Measured runs",
"title": "Agent calls",
"contract_version": {
"major": 1,
"minor": 0,
Expand Down Expand Up @@ -90,7 +90,7 @@
{
"component_id": "overview-tokens",
"component_kind": "metric_card",
"title": "Tokens in and out",
"title": "Tokens processed",
"contract_version": {
"major": 1,
"minor": 0,
Expand Down
12 changes: 6 additions & 6 deletions src/pages/local/overview.page.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"widgets": [
{
"widget_id": "2157c330-49a4-4d41-a61f-cc0c8c4a0ddb",
"title": "Spend",
"title": "Actual spend",
"row": 0,
"col": 0,
"width": 4,
Expand All @@ -23,7 +23,7 @@
"config_kind": "metric_card",
"widget_type": "metric_card",
"metric_key": "spend_usd",
"label": "Spend",
"label": "Actual spend",
"unit": "USD",
"format": "currency",
"precision": 4
Expand All @@ -49,7 +49,7 @@
},
{
"widget_id": "b13119df-d621-5b7d-b199-8162fed59c5c",
"title": "Measured runs",
"title": "Agent calls",
"row": 0,
"col": 8,
"width": 4,
Expand All @@ -59,15 +59,15 @@
"config_kind": "metric_card",
"widget_type": "metric_card",
"metric_key": "runs_measured",
"label": "Measured runs",
"label": "Agent calls",
"unit": "runs",
"format": "number",
"precision": 0
}
},
{
"widget_id": "869ee6a5-22b1-5225-9975-81de4d5a5fcc",
"title": "Tokens in and out",
"title": "Tokens processed",
"row": 2,
"col": 0,
"width": 4,
Expand All @@ -77,7 +77,7 @@
"config_kind": "metric_card",
"widget_type": "metric_card",
"metric_key": "tokens_in_and_out",
"label": "Tokens in and out",
"label": "Tokens processed",
"unit": "tokens",
"format": "number",
"precision": 0
Expand Down
16 changes: 8 additions & 8 deletions tests/e2e/local-dashboard-contracts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,18 +216,18 @@ test('Overview is the default page and fits 1440x900 with no unmeasured 0 (AC4)'
await expect(savings.getByText('$1.2252', { exact: true })).toHaveCount(0);
await expect(savings.getByText('Baseline claude-sonnet-5-5 · pricing manifest v3', { exact: true })).toBeVisible();
await expect(savings.getByText('$0.4000', { exact: true })).toHaveCount(0);
// Amendment 2: Spend, Measured runs and Tokens read the same all row (45 measured of 46, not
// Amendment 2: Actual spend, Agent calls and Tokens processed (OMN-20758 titles) read the same all row (45 measured of 46, not
// cost.savings-overview.v1's 46; the two token columns side by side, never a combined total).
const headline = (title: string) => page.locator('.local-dashboard-panel').filter({ has: page.getByRole('heading', { name: title, exact: true }) });
await expect(headline('Spend').getByText('$0.0069', { exact: true })).toBeVisible();
await expect(headline('Measured runs').getByText('45', { exact: true })).toBeVisible();
await expect(headline('Measured runs').getByText('46', { exact: true })).toHaveCount(0);
await expect(headline('Measured runs').getByText('Of 46 runs · Unknown-token runs excluded: 1 · Unknown-spend runs excluded: 0', { exact: true })).toBeVisible();
await expect(headline('Tokens in and out').getByText('7,452 in · 2,392 out', { exact: true })).toBeVisible();
await expect(headline('Tokens in and out').getByText(/Not served yet/)).toHaveCount(0);
await expect(headline('Actual spend').getByText('$0.0069', { exact: true })).toBeVisible();
await expect(headline('Agent calls').getByText('45', { exact: true })).toBeVisible();
await expect(headline('Agent calls').getByText('46', { exact: true })).toHaveCount(0);
await expect(headline('Agent calls').getByText('Of 46 runs · Unknown-token runs excluded: 1 · Unknown-spend runs excluded: 0', { exact: true })).toBeVisible();
await expect(headline('Tokens processed').getByText('7,452 in · 2,392 out', { exact: true })).toBeVisible();
await expect(headline('Tokens processed').getByText(/Not served yet/)).toHaveCount(0);

// The headline panels sit above the fold; Recent runs starts above it.
for (const title of ['Spend', 'Savings', 'Measured runs', 'Last run']) {
for (const title of ['Actual spend', 'Savings', 'Agent calls', 'Last run']) {
const bounds = await page.locator('.local-dashboard-panel').filter({ has: page.getByRole('heading', { name: title, exact: true }) }).boundingBox();
expect(bounds, title).not.toBeNull();
expect(bounds!.y + bounds!.height, title).toBeLessThanOrEqual(900);
Expand Down
Loading