Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
af8174c
feat(overview): fit the matrix on one screen and present it full screen
chunfangamd Aug 11, 2026
3ac67b9
fix(overview): keep the dormant row scope on the URL
chunfangamd Aug 11, 2026
a02d659
Merge remote-tracking branch 'origin/master' into chun/inferencex-app…
github-actions[bot] Aug 11, 2026
5523298
fix(overview): update /api/v1/overview route digest after merge
github-actions[bot] Aug 11, 2026
f4d5f6b
Merge branch 'master' into chun/inferencex-app_overview
adibarra Aug 11, 2026
90dcd5a
chore(api): re-register the overview route digest after the row-scope…
chunfangamd Aug 11, 2026
334556b
test(overview): assert the DOM this branch actually renders
chunfangamd Aug 11, 2026
8962576
Merge master and keep the deck rendering below the xl breakpoint
chunfangamd Aug 11, 2026
8736e9e
Merge master to bring the branch up to date
chunfangamd Aug 11, 2026
d6670da
fix(overview): key the client data cache on the row scopes
chunfangamd Aug 11, 2026
872ef27
Merge master to bring the branch up to date
chunfangamd Aug 11, 2026
a64befc
feat(overview): merge the matrix footer into one bar and dim stale da…
edwingao28 Aug 14, 2026
db12048
Merge chun's fork head (cache-key fix + master) into the footer-bar p…
edwingao28 Aug 14, 2026
1a1c7ab
test(overview): assert the flipped row-filter chip by its accessible …
edwingao28 Aug 14, 2026
6b30464
Merge master to bring the branch up to date
edwingao28 Aug 15, 2026
02b31b6
fix(overview): page one view per arrow press while presenting
edwingao28 Aug 15, 2026
dcb1fcc
fix(overview): render the window menu in place while presenting
edwingao28 Aug 15, 2026
3537f8b
fix(overview): name the active window in the row-filter copy
edwingao28 Aug 15, 2026
d298e1f
Merge branch 'master' into chun/inferencex-app_overview
edwingao28 Aug 15, 2026
831fec8
Merge master to pick up the #736 test realignment
edwingao28 Aug 15, 2026
51584b6
chore: reconcile parallel master merges
edwingao28 Aug 15, 2026
a68c232
Merge branch 'master' into chun/inferencex-app_overview
edwingao28 Aug 16, 2026
a4f2042
Merge branch 'master' into chun/inferencex-app_overview
edwingao28 Aug 16, 2026
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
90 changes: 77 additions & 13 deletions packages/app/cypress/e2e/overview.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const MATRIX_ROWS = 8;
const AGENTX = 'agentx';
const AGENTX_LABEL = 'Long Context Multi-Turn Realistic Agentic Scenario (AgentX)';
const AGENTX_LABEL_ZH = '长上下文多轮真实智能体场景(AgentX)';
/** Shared by both locales: the scenario is named after its acronym. */
const AGENTX_SHORT = 'AgentX';

const PAGE_TITLE = 'Inference Cost per Million Tokens';
const PAGE_TITLE_ZH = '推理每百万 token 成本';
Expand Down Expand Up @@ -61,6 +63,21 @@ function expectNoHorizontalScroller(testId: string) {
});
}

/**
* The row header shows the acronym so the 22%-wide model column stays one line,
* and keeps the full scenario name for assistive tech and the hover title. Both
* strings are in the cell, so assert the two layers rather than their
* concatenation. Scenarios already named by a short label render one node.
*/
function expectAgentxScenario(fullLabel: string) {
cy.get('[data-testid="overview-model-scenario"]')
.should('have.attr', 'title', fullLabel)
.within(() => {
cy.get('.sr-only').should('have.text', fullLabel);
cy.get('[aria-hidden="true"]').should('have.text', AGENTX_SHORT);
});
}

/** Visible dates and snapshot framing must be gone; evidence stays in labels. */
function expectNoVisibleDatesOrSnapshot() {
cy.get('[data-testid="overview-pair-evidence-date"]').should('not.exist');
Expand Down Expand Up @@ -354,10 +371,12 @@ describe('Overview page', () => {
cy.viewport(1280, 900);
cy.visit('/zh/overview?models=all');

cy.get('[data-testid="overview-model-scope-toggle"]').should(
'contain.text',
'隐藏已弃用与维护模式模型',
);
// The chip carries the short label; the full sentence stays on the
// accessible name and hover title.
cy.get('[data-testid="overview-model-scope-toggle"]')
.should('contain.text', '隐藏停用模型')
.find('[data-overview-model-scope="default"]')
.should('have.attr', 'aria-label', '隐藏已弃用与维护模式模型');
desktopModel('gpt-oss-120b')
.find('[data-testid="overview-model-category-badge"]')
.should('contain.text', '已弃用')
Expand Down Expand Up @@ -415,8 +434,11 @@ describe('Overview page', () => {
cy.viewport(1280, 900);
cy.visit('/overview');

cy.get('[data-testid="overview-page"]')
.children('[data-testid="overview-comparison-switcher"]')
// The tabs sit inside the surface handed to the Fullscreen API so that
// presenting keeps them with the matrix, which costs them their old spot as
// a direct child of the page.
cy.get('[data-testid="overview-presentation-surface"]')
.find('[data-testid="overview-comparison-switcher"]')
.should('have.length', 1)
.and('have.class', 'justify-center');
cy.get('[data-testid="overview-comparison-switcher"]')
Expand Down Expand Up @@ -631,6 +653,48 @@ describe('Overview page', () => {
}
});

// Clicking the control, not just building its href: the matrix reads from a
// client data cache, and a cache keyed without the row params moves the
// address bar while leaving every row on screen.
it('narrows the matrix when the row filter is clicked in either comparison mode', () => {
cy.viewport(1280, 900);

for (const [href, attribute, key, sentence] of [
['/overview?compare=30d', 'data-overview-row-scope', 'rows=changed', 'no 30-day change'],
[
'/overview',
'data-overview-hardware-row-scope',
'hwrows=priced',
'no result on any platform',
],
] as const) {
cy.visit(href);
cy.get('[data-testid="overview-desktop-model"]').should('have.length', MATRIX_ROWS);

cy.get(`a[${attribute}]`)
.should('contain.text', 'Hide ')
.invoke('text')
.then((label) => {
const hidden = Number(/\d+/.exec(label)?.[0]);
expect(hidden, `hidden row count in "${label}"`).to.be.greaterThan(0);

cy.get(`a[${attribute}]`).click();
cy.location('search').should('contain', key);
cy.get('[data-testid="overview-desktop-model"]').should(
'have.length',
MATRIX_ROWS - hidden,
);
// The action label names the click, so it flips once the scope
// lands; the chip keeps the short label and the counted sentence
// rides the accessible name.
cy.get(`a[${attribute}]`)
.should('contain.text', 'Show all rows')
.and('contain.text', String(hidden))
.and('have.attr', 'aria-label', `Show ${hidden} rows with ${sentence}`);
});
}
});

it('defaults to community engine scope and switches with canonical links preserving tier and locale', () => {
cy.viewport(1280, 900);
cy.visit('/overview');
Expand Down Expand Up @@ -660,7 +724,7 @@ describe('Overview page', () => {
});

desktopModel('GLM-5.2').within(() => {
cy.get('[data-testid="overview-model-scenario"]').should('have.text', AGENTX_LABEL);
expectAgentxScenario(AGENTX_LABEL);
cy.get('[data-testid="overview-pair-missing"]').should('have.length', 5);
});
cy.get(
Expand Down Expand Up @@ -900,9 +964,9 @@ describe('Overview page', () => {
cy.get('[data-testid="overview-desktop-matrix"]').should('contain.text', label);
}
for (const model of ['Kimi-K3', 'GLM-5.2']) {
desktopModel(model)
.find('[data-testid="overview-model-scenario"]')
.should('have.text', AGENTX_LABEL);
desktopModel(model).within(() => {
expectAgentxScenario(AGENTX_LABEL);
});
}
for (const model of ['DeepSeek-V4-Pro', 'MiniMax-M3', 'Qwen-3.5-397B-A17B']) {
desktopModel(model, SINGLE_TURN)
Expand All @@ -925,7 +989,7 @@ describe('Overview page', () => {
});

desktopModel('DeepSeek-V4-Pro', AGENTX).within(() => {
cy.get('[data-testid="overview-model-scenario"]').should('have.text', AGENTX_LABEL);
expectAgentxScenario(AGENTX_LABEL);
cy.contains('DeepSeek V4 Pro 1.6T').should('exist');
// Priced from the AgentX rows alone — the single-turn sweep never leaks in.
cy.get(
Expand Down Expand Up @@ -1440,14 +1504,14 @@ describe('Overview page', () => {
.and('have.attr', 'title', '缺少可比较的 B200 基线');
});
desktopModel('GLM-5.2').within(() => {
cy.get('[data-testid="overview-model-scenario"]').should('have.text', AGENTX_LABEL_ZH);
expectAgentxScenario(AGENTX_LABEL_ZH);
cy.get('[data-testid="overview-pair-missing"]').should('have.length', 5);
platform('b300')
.find('[data-testid="overview-pair-missing"]')
.should('contain.text', '该场景暂无数据');
});
desktopModel('DeepSeek-V4-Pro', AGENTX).within(() => {
cy.get('[data-testid="overview-model-scenario"]').should('have.text', AGENTX_LABEL_ZH);
expectAgentxScenario(AGENTX_LABEL_ZH);
cy.get(
'[data-testid="overview-pair-value"][data-hardware="b200"] [data-testid="overview-cost-evidence-link"]',
).should('have.text', '$0.064');
Expand Down
20 changes: 17 additions & 3 deletions packages/app/src/app/api/v1/overview/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,32 @@ describe('GET /api/v1/overview', () => {
mockGetOverviewPageData.mockResolvedValueOnce(data);

const response = await GET(
request('/api/v1/overview?tier=75&engine=all&compare=30d&ref=b300&models=all'),
request(
'/api/v1/overview?tier=75&engine=all&compare=30d&ref=b300&models=all&rows=changed&hwrows=priced',
),
);

expect(response.status).toBe(200);
expect(await response.json()).toEqual(data);
expect(mockGetOverviewPageData).toHaveBeenCalledWith(75, 'all', '30d', 'b300', 'all');
expect(mockGetOverviewPageData).toHaveBeenCalledWith(
75,
'all',
'30d',
'b300',
'all',
'changed',
'priced',
);
expect(mockCachedJson).toHaveBeenCalledWith(data);
});

it('normalizes unsupported query values to overview defaults', async () => {
mockGetOverviewPageData.mockResolvedValueOnce({ models: [] });

await GET(
request('/api/v1/overview?tier=999&engine=vendor&compare=weekly&ref=h100&models=inactive'),
request(
'/api/v1/overview?tier=999&engine=vendor&compare=weekly&ref=h100&models=inactive&rows=some&hwrows=blank',
),
);

expect(mockGetOverviewPageData).toHaveBeenCalledWith(
Expand All @@ -59,6 +71,8 @@ describe('GET /api/v1/overview', () => {
'hardware',
'b200',
'default',
'all',
'all',
);
});

Expand Down
4 changes: 4 additions & 0 deletions packages/app/src/app/api/v1/overview/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { cachedJson } from '@/lib/api-cache';
import {
resolveOverviewComparisonMode,
resolveOverviewEngineScope,
resolveOverviewHardwareRowScope,
resolveOverviewModelScope,
resolveOverviewReferenceHardware,
resolveOverviewRowScope,
resolveOverviewTier,
} from '@/lib/overview-data';
import { getOverviewPageData } from '@/lib/overview-data.server';
Expand All @@ -22,6 +24,8 @@ export async function GET(request: NextRequest) {
resolveOverviewComparisonMode(params.get('compare') ?? undefined),
resolveOverviewReferenceHardware(params.get('ref') ?? undefined),
resolveOverviewModelScope(params.get('models') ?? undefined),
resolveOverviewRowScope(params.get('rows') ?? undefined),
resolveOverviewHardwareRowScope(params.get('hwrows') ?? undefined),
);
return cachedJson(data);
} catch (error) {
Expand Down
4 changes: 4 additions & 0 deletions packages/app/src/app/overview/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import { enAlternates } from '@/lib/i18n';
import {
resolveOverviewComparisonMode,
resolveOverviewEngineScope,
resolveOverviewHardwareRowScope,
resolveOverviewModelScope,
resolveOverviewReferenceHardware,
resolveOverviewRowScope,
resolveOverviewTier,
} from '@/lib/overview-data';
import { getOverviewPageData } from '@/lib/overview-data.server';
Expand Down Expand Up @@ -47,6 +49,8 @@ export default async function OverviewPage({ searchParams }: Props) {
resolveOverviewComparisonMode(sp.compare),
resolveOverviewReferenceHardware(sp.ref),
resolveOverviewModelScope(sp.models),
resolveOverviewRowScope(sp.rows),
resolveOverviewHardwareRowScope(sp.hwrows),
);
return <OverviewPageContent data={data} locale="en" />;
}
4 changes: 4 additions & 0 deletions packages/app/src/app/zh/overview/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import { ZH_OG_LOCALE, zhAlternates } from '@/lib/i18n';
import {
resolveOverviewComparisonMode,
resolveOverviewEngineScope,
resolveOverviewHardwareRowScope,
resolveOverviewModelScope,
resolveOverviewReferenceHardware,
resolveOverviewRowScope,
resolveOverviewTier,
} from '@/lib/overview-data';
import { getOverviewPageData } from '@/lib/overview-data.server';
Expand Down Expand Up @@ -48,6 +50,8 @@ export default async function ZhOverviewPage({ searchParams }: Props) {
resolveOverviewComparisonMode(sp.compare),
resolveOverviewReferenceHardware(sp.ref),
resolveOverviewModelScope(sp.models),
resolveOverviewRowScope(sp.rows),
resolveOverviewHardwareRowScope(sp.hwrows),
);
return <OverviewPageContent data={data} locale="zh" />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { track } from '@/lib/analytics';
import type { OverviewHistoryWindowKey } from '@/lib/overview-data';

import { useOverviewComparisonMode, useOverviewNavigation } from './overview-navigation';
import { useIsPresenting } from './overview-presentation';

interface WindowOption {
href: string;
Expand All @@ -34,6 +35,10 @@ export function OverviewHistoryWindowSelect({
// back to hardware mode carries no window, so keep the committed one.
const pendingMode = useOverviewComparisonMode();
const value = pendingMode === 'hardware' ? committedValue : pendingMode;
// Same reason as the reference select: `document.body` sits outside both the
// fullscreen element and the CSS `zoom`, so a portalled menu would be
// invisible or unscaled while presenting.
const presenting = useIsPresenting();

return (
<Select
Expand All @@ -53,7 +58,7 @@ export function OverviewHistoryWindowSelect({
>
<SelectValue>{options.find((option) => option.value === value)?.label}</SelectValue>
</SelectTrigger>
<SelectContent align="center">
<SelectContent align="center" portalled={!presenting}>
{options.map((option) => (
<SelectItem key={option.value} value={option.value} data-overview-window={option.value}>
{option.label}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { useOverviewNavigation } from './overview-navigation';
const PREFETCH_DWELL_MS = 120;

interface OverviewNavAnalytics {
control: 'comparison' | 'engine' | 'models' | 'tier';
control: 'comparison' | 'engine' | 'hwrows' | 'models' | 'rows' | 'tier';
value: string;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ let root: Root;
let selectTier: (() => void) | undefined;
let selectEngine: (() => void) | undefined;
let selectReference: (() => void) | undefined;
let selectRowScope: (() => void) | undefined;
let selectHardwareRowScope: (() => void) | undefined;
let prefetchTier: (() => void) | undefined;

function pageData(tier: OverviewTier): OverviewPageData {
Expand All @@ -43,6 +45,10 @@ function pageData(tier: OverviewTier): OverviewPageData {
comparisonMode: 'hardware',
referenceHardware: 'b200',
modelScope: 'default',
rowScope: 'all',
hardwareRowScope: 'all',
unchangedRowCount: 0,
emptyRowCount: 0,
historicalWindow: null,
};
}
Expand All @@ -54,6 +60,8 @@ function Probe() {
selectTier = () => navigation.push('/overview?tier=75', ['tier']);
selectEngine = () => navigation.push('/overview?engine=all', ['engine']);
selectReference = () => navigation.push('/overview?ref=b300', ['ref']);
selectRowScope = () => navigation.push('/overview?compare=30d&rows=changed', ['rows']);
selectHardwareRowScope = () => navigation.push('/overview?hwrows=priced', ['hwrows']);
prefetchTier = () => navigation.prefetch('/overview?tier=75', ['tier']);
return (
<>
Expand Down Expand Up @@ -105,6 +113,8 @@ afterEach(() => {
selectTier = undefined;
selectEngine = undefined;
selectReference = undefined;
selectRowScope = undefined;
selectHardwareRowScope = undefined;
prefetchTier = undefined;
vi.unstubAllGlobals();
});
Expand Down Expand Up @@ -384,6 +394,32 @@ describe('OverviewNavigationProvider', () => {
expect(readProbe('reference')).toBe('b300');
});

// Both row scopes narrow the rows the server sends, so neither may collapse
// into the unfiltered payload's cache key the way `ref` deliberately does.
it('requests a narrowed matrix for each row scope instead of reusing the full one', () => {
deferredFetch();

renderProvider(pageData(50), '/overview?compare=30d');
act(() => selectRowScope?.());

expect(fetch).toHaveBeenCalledWith('/api/v1/overview?compare=30d&rows=changed', {
headers: { Accept: 'application/json' },
});
expect(readProbe('pending')).toBe('pending');
});

it('requests a narrowed matrix for the hardware row scope', () => {
deferredFetch();

renderProvider(pageData(50), '/overview');
act(() => selectHardwareRowScope?.());

expect(fetch).toHaveBeenCalledWith('/api/v1/overview?hwrows=priced', {
headers: { Accept: 'application/json' },
});
expect(readProbe('pending')).toBe('pending');
});

it('preserves unknown params and the fragment on the first selection', () => {
deferredFetch();

Expand Down
Loading