From 72cce92ab7a822b0cf87c3e0f28889cc192ef578 Mon Sep 17 00:00:00 2001
From: Oseltamivir <58582368+Oseltamivir@users.noreply.github.com>
Date: Mon, 26 Oct 2026 14:23:28 +0800
Subject: [PATCH 1/5] feat: label MiniMax M3 charts as historical
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Keep a bilingual submissions-closed note beside source attribution in chart captures and PNG exports. Cover official data, unofficial overlays, and Chinese mobile rendering; document the presentation-only status in the API reference and packaged skill.
中文:在 MiniMax M3 图表的来源标注旁显示中英文“历史基准测试(已停止接收提交)”提示,并保留在截图和 PNG 导出中。测试覆盖正式数据、非官方 overlay 和中文移动端;同步说明 API 数据保持不变。
---
.../app/cypress/e2e/inference-chart.cy.ts | 88 +++++++++++++++++++
.../components/inference/ui/ChartDisplay.tsx | 9 +-
.../src/components/ui/result-context.test.tsx | 19 ++++
.../app/src/components/ui/result-context.tsx | 15 +++-
packages/app/src/hooks/useChartExport.test.ts | 31 +++++++
.../app/src/lib/views-api/docs/options.ts | 4 +-
.../skills/inferencex-api/integrity.json | 2 +-
.../references/dashboard-views.md | 11 ++-
8 files changed, 173 insertions(+), 6 deletions(-)
diff --git a/packages/app/cypress/e2e/inference-chart.cy.ts b/packages/app/cypress/e2e/inference-chart.cy.ts
index 9ad7e9b61..a6bf19a03 100644
--- a/packages/app/cypress/e2e/inference-chart.cy.ts
+++ b/packages/app/cypress/e2e/inference-chart.cy.ts
@@ -14,6 +14,7 @@ import {
} from '../support/e2e';
import {
interceptOverlayRun,
+ AGENTIC_DATE,
OVERLAY_RUN_ID,
OVERLAY_RUN_URL,
SINGLE_TURN_DATE,
@@ -154,6 +155,7 @@ describe('Inference Chart', () => {
.and('not.contain.text', 'Workload:')
.and('not.contain.text', 'Precision:')
.and('not.contain.text', 'Metric:');
+ cy.get('[data-testid="result-context-source-note"]').should('not.exist');
});
it('plots OpenRouter-priced token revenue for official and unofficial runs', () => {
@@ -388,6 +390,92 @@ describe('Inference Chart', () => {
});
});
+describe('MiniMax M3 historical benchmark attribution', () => {
+ it('keeps the status beside the source for official charts, overlays, and the Chinese mobile chart', () => {
+ const model = 'minimaxm3';
+ const officialRows = b300Rows(null).map((row) => ({ ...row, model }));
+ const overlayRows = b300Rows(OVERLAY_RUN_URL).map((row) => ({ ...row, model }));
+ cy.intercept('GET', '/api/v1/availability', {
+ body: officialRows.map(({ metrics: _metrics, ...row }) => row),
+ });
+ cy.intercept('GET', '/api/v1/benchmarks*', { body: officialRows });
+ cy.intercept('GET', '/api/unofficial-run*', {
+ body: {
+ runInfos: [
+ {
+ id: OVERLAY_RUN_ID,
+ name: 'historical-m3-overlay',
+ branch: 'historical-m3-overlay',
+ sha: 'abc000',
+ createdAt: `${AGENTIC_DATE}T00:00:00Z`,
+ url: OVERLAY_RUN_URL,
+ conclusion: 'success',
+ status: 'completed',
+ isNonMainBranch: true,
+ },
+ ],
+ benchmarks: overlayRows,
+ evaluations: [],
+ },
+ }).as('historicalM3Overlay');
+
+ cy.visit('/inference/minimax-m3?i_seq=agentic-traces', {
+ onBeforeLoad: unlockAgenticGate,
+ });
+ cy.get('#chart-0 [data-testid="result-context"]')
+ .should('contain.text', 'Source: SemiAnalysis InferenceX™')
+ .and('contain.text', 'Historical benchmark — submissions closed');
+ cy.get('#chart-0 [data-testid="result-context-source-note"]').should('be.visible');
+ let exportedPng = '';
+ let exportedCaption = '';
+ cy.window().then((win) => {
+ win.HTMLAnchorElement.prototype.click = function () {
+ if (this.download.endsWith('.png')) {
+ exportedPng = this.href;
+ exportedCaption = win.document.querySelector('#chart-0-export')?.textContent ?? '';
+ }
+ };
+ });
+ cy.get('[data-testid="chart-figure"] [data-testid="export-button"]').first().click();
+ cy.get('[data-testid="export-png-button"]').click();
+ cy.window()
+ .should(() => expect(exportedPng).to.match(/^data:image\/png;base64,/u))
+ .then(() => {
+ expect(exportedCaption).to.contain('Historical benchmark — submissions closed');
+ cy.writeFile(
+ 'cypress/downloads/minimax-m3-historical.png',
+ exportedPng.split(',')[1],
+ 'base64',
+ );
+ });
+
+ cy.visit(`/inference/minimax-m3?i_seq=agentic-traces&unofficialrun=${OVERLAY_RUN_ID}`, {
+ onBeforeLoad: unlockAgenticGate,
+ });
+ cy.wait('@historicalM3Overlay');
+ cy.get('#chart-0 .unofficial-overlay-pt').should('have.length.greaterThan', 0);
+ cy.get('#chart-0 [data-testid="result-context-source-note"]').should(
+ 'have.text',
+ ' · Historical benchmark — submissions closed',
+ );
+ cy.get('[aria-label="Dismiss historical-m3-overlay"]').click();
+ cy.get('#chart-0 .unofficial-overlay-pt').should('not.exist');
+ cy.get('#chart-0 [data-testid="result-context-source-note"]').should('be.visible');
+
+ cy.viewport(375, 900);
+ cy.visit(`/zh/inference/minimax-m3?i_seq=agentic-traces&unofficialrun=${OVERLAY_RUN_ID}`, {
+ onBeforeLoad: unlockAgenticGate,
+ });
+ cy.wait('@historicalM3Overlay');
+ cy.get('#chart-0 [data-testid="result-context-source-note"]')
+ .should('be.visible')
+ .and('have.text', ' · 历史基准测试(已停止接收提交)');
+ cy.document().then((doc) => {
+ expect(doc.documentElement.scrollWidth).to.be.at.most(doc.documentElement.clientWidth);
+ });
+ });
+});
+
describe('Inference Chart — Simplified Chinese mobile path', () => {
beforeEach(() => {
cy.viewport(375, 900);
diff --git a/packages/app/src/components/inference/ui/ChartDisplay.tsx b/packages/app/src/components/inference/ui/ChartDisplay.tsx
index ca8ba5ed3..d8ffcc72a 100644
--- a/packages/app/src/components/inference/ui/ChartDisplay.tsx
+++ b/packages/app/src/components/inference/ui/ChartDisplay.tsx
@@ -79,7 +79,7 @@ import {
useUnofficialRun,
} from '@/providers/unofficial-run-provider';
import {
- type Model,
+ Model,
type Precision,
Sequence,
getModelLabel,
@@ -136,6 +136,7 @@ const STRINGS = {
table: 'Table',
sourceUnofficial: 'Source: UNOFFICIAL',
sourceOfficial: 'Source: SemiAnalysis InferenceX™',
+ historicalBenchmarkStatus: 'Historical benchmark — submissions closed',
revenuePrices: (input: string, cached: string | null, output: string) =>
cached === null
? `Input $${input}/M tok · Output $${output}/M tok`
@@ -167,6 +168,7 @@ const STRINGS = {
table: '表格',
sourceUnofficial: '来源:非官方',
sourceOfficial: '来源:SemiAnalysis InferenceX™',
+ historicalBenchmarkStatus: '历史基准测试(已停止接收提交)',
revenuePrices: (input: string, cached: string | null, output: string) =>
cached === null
? `输入 $${input}/百万 token · 输出 $${output}/百万 token`
@@ -1121,6 +1123,11 @@ export default function ChartDisplay({ embedded = false }: { embedded?: boolean
: 'SemiAnalysis InferenceX™ + UNOFFICIAL overlays'
: 'SemiAnalysis InferenceX™'
}
+ sourceNote={
+ graph.model === Model.MiniMax_M3
+ ? t.historicalBenchmarkStatus
+ : undefined
+ }
costBasis={
usesTokenSalePricing(selectedYAxisMetric) && tokenRevenuePricing
? t.revenuePrices(
diff --git a/packages/app/src/components/ui/result-context.test.tsx b/packages/app/src/components/ui/result-context.test.tsx
index e60e54b72..f434926c0 100644
--- a/packages/app/src/components/ui/result-context.test.tsx
+++ b/packages/app/src/components/ui/result-context.test.tsx
@@ -71,6 +71,25 @@ describe('ResultContext', () => {
);
});
+ it('keeps a historical status beside its source in the visible and exportable caption', () => {
+ for (const [locale, status] of [
+ ['en', 'Historical benchmark — submissions closed'],
+ ['zh', '历史基准测试(已停止接收提交)'],
+ ] as const) {
+ const container = document.createElement('div');
+ act(() => {
+ createRoot(container).render(
+ ,
+ );
+ });
+ const note = container.querySelector('[data-testid="result-context-source-note"]');
+ expect(note?.textContent).toBe(` · ${status}`);
+ expect(note?.closest('dd')?.textContent).toBe(`SemiAnalysis InferenceX™ · ${status}`);
+ expect(container.cloneNode(true).textContent).toContain(status);
+ expect(note?.closest('.no-export')).toBeNull();
+ }
+ });
+
it('hosts an inline Cost Tier control and keeps a plain-text twin for PNG export', () => {
const container = document.createElement('div');
act(() => {
diff --git a/packages/app/src/components/ui/result-context.tsx b/packages/app/src/components/ui/result-context.tsx
index 7853ed85f..250925976 100644
--- a/packages/app/src/components/ui/result-context.tsx
+++ b/packages/app/src/components/ui/result-context.tsx
@@ -24,6 +24,7 @@ export interface ResultContextProps {
dates?: readonly string[];
dateRange?: { start: string; end: string };
source?: string;
+ sourceNote?: string;
costBasis?: string;
costBasisTestId?: string;
}
@@ -83,6 +84,7 @@ export function ResultContext({
dates,
dateRange,
source,
+ sourceNote,
costBasis,
costBasisTestId,
}: ResultContextProps) {
@@ -204,7 +206,18 @@ export function ResultContext({
{source && (
{labels.source}:{' '}
- {source}
+
+ {source}
+ {sourceNote && (
+
+ {' · '}
+ {sourceNote}
+
+ )}
+
)}
{costBasis && (
diff --git a/packages/app/src/hooks/useChartExport.test.ts b/packages/app/src/hooks/useChartExport.test.ts
index 9c882e100..75545b359 100644
--- a/packages/app/src/hooks/useChartExport.test.ts
+++ b/packages/app/src/hooks/useChartExport.test.ts
@@ -16,6 +16,7 @@ import {
normalizeChartSvgWidthsForExport,
useChartExport,
} from './useChartExport';
+import { ResultContext } from '@/components/ui/result-context';
describe('useChartExport failure messages', () => {
let root: Root;
@@ -89,6 +90,36 @@ describe('useChartExport failure messages', () => {
expect(chart.textContent).toBe('DeepSeek R1');
});
+ it('includes the historical source note in the PNG capture clone', async () => {
+ const captionRoot = createRoot(chart);
+ act(() => {
+ captionRoot.render(
+ createElement(ResultContext, {
+ locale: 'en',
+ source: 'SemiAnalysis InferenceX™',
+ sourceNote: 'Historical benchmark — submissions closed',
+ }),
+ );
+ });
+ let snapshot: HTMLElement | undefined;
+ exportMocks.toPng.mockImplementationOnce((element: HTMLElement) => {
+ snapshot = element.cloneNode(true) as HTMLElement;
+ throw new Error('stop after capture');
+ });
+ vi.spyOn(window, 'alert').mockImplementation(() => {});
+ vi.spyOn(console, 'error').mockImplementation(() => {});
+
+ await act(async () => {
+ await current.exportToImage();
+ });
+
+ expect(snapshot?.querySelector('[data-testid="result-context-source-note"]')?.textContent).toBe(
+ ' · Historical benchmark — submissions closed',
+ );
+ expect(snapshot?.textContent).toContain('Source: SemiAnalysis InferenceX™');
+ act(() => captionRoot.unmount());
+ });
+
it.each([true, false])(
'omits the legend column and keeps official/overlay labels when the sidebar is open=%s',
async (open) => {
diff --git a/packages/app/src/lib/views-api/docs/options.ts b/packages/app/src/lib/views-api/docs/options.ts
index dbd471f94..1c7e4e0b9 100644
--- a/packages/app/src/lib/views-api/docs/options.ts
+++ b/packages/app/src/lib/views-api/docs/options.ts
@@ -239,8 +239,8 @@ export const operations: ApiOperation[] = [
path: '/api/v1/views/options',
summary: text('Discover every views-API option domain', '获取 views API 的全部选项域'),
description: text(
- 'Static discovery endpoint for the views API: models (with DB keys, category and release date), sequences, precisions, hardware (with vendor, architecture, TDP and per-provider hourly cost), frameworks, speculative-decoding methods, percentiles, x-axis and scale modes, chart metrics (bilingual labels, unit, polarity), quick-filter domains, reliability ranges, and overview/calculator/fleet option sets — plus the defaults the dashboard itself uses. Values come from the same registries the dashboard renders its controls from, so this response is the authoritative input catalog for the other /api/v1/views endpoints. No database access.',
- 'views API 的静态发现端点:模型(含数据库键、分类与发布日期)、序列、精度、硬件(含厂商、架构、TDP 及各供应商每小时成本)、推理框架、投机解码方法、百分位、x 轴与坐标缩放模式、图表指标(中英文标签、单位、极性)、快捷筛选域、可靠性时间范围,以及 overview/calculator/fleet 的选项集,并附仪表盘实际使用的默认值。所有取值直接来自仪表盘控件渲染所用的注册表,因此该响应是其余 /api/v1/views 端点的权威输入目录。不访问数据库。',
+ 'Static discovery endpoint for the views API: models (with DB keys, category and release date), sequences, precisions, hardware (with vendor, architecture, TDP and per-provider hourly cost), frameworks, speculative-decoding methods, percentiles, x-axis and scale modes, chart metrics (bilingual labels, unit, polarity), quick-filter domains, reliability ranges, and overview/calculator/fleet option sets — plus the defaults the dashboard itself uses. Values come from the same registries the dashboard renders its controls from, so this response is the authoritative input catalog for the other /api/v1/views endpoints. MiniMax M3 has category deprecated; its historical-submissions-closed chart label is presentation only and does not change view data. No database access.',
+ 'views API 的静态发现端点:模型(含数据库键、分类与发布日期)、序列、精度、硬件(含厂商、架构、TDP 及各供应商每小时成本)、推理框架、投机解码方法、百分位、x 轴与坐标缩放模式、图表指标(中英文标签、单位、极性)、快捷筛选域、可靠性时间范围,以及 overview/calculator/fleet 的选项集,并附仪表盘实际使用的默认值。所有取值直接来自仪表盘控件渲染所用的注册表,因此该响应是其余 /api/v1/views 端点的权威输入目录。MiniMax M3 的分类为 deprecated;图表中“历史基准测试(已停止接收提交)”的标注仅用于展示,不改变视图数据。不访问数据库。',
),
audience: 'public',
stability: 'beta',
diff --git a/packages/skills/skills/inferencex-api/integrity.json b/packages/skills/skills/inferencex-api/integrity.json
index cbcac8a4e..f1803e33a 100644
--- a/packages/skills/skills/inferencex-api/integrity.json
+++ b/packages/skills/skills/inferencex-api/integrity.json
@@ -9,7 +9,7 @@
"references/cli-contract.md": "4f070877eeefffad2860c58f79f68432fbe27aa21fc578a1c696f0309a3d7d7b",
"references/cli.md": "11e1183f7ed70d669019e8435ac16a6a9f873de027507b8850a9943f6659bfd9",
"references/collectivex.md": "f79cf8e2779a9289ab69f79f4a4b338e0b9a5f76a3750a3e969f33a744e726a1",
- "references/dashboard-views.md": "1e2a0848c4efb9bca66fbf708082aa87402f32fff43fcda3d0638658cba0b90b",
+ "references/dashboard-views.md": "928ed7a1a1b44006ad5ebf12952560eb1e5459cd85257cabffa7e8cc2dd5fe3a",
"references/offline-exports.md": "95aa565dcd4c9e592159baa560a9a58217bc61e395de1c6daf0576795c7f4ec9",
"references/pareto.md": "1b4d2d163f982e3f2d97addce310789eae9c1e5badd82dd7501708c9e0385027",
"references/powerx.md": "5af5527d54a7e6191a5769080c122638b333b1ceb59e19fdee7df662a5cd3c8f",
diff --git a/packages/skills/skills/inferencex-api/references/dashboard-views.md b/packages/skills/skills/inferencex-api/references/dashboard-views.md
index 0f2955f16..41df9edf6 100644
--- a/packages/skills/skills/inferencex-api/references/dashboard-views.md
+++ b/packages/skills/skills/inferencex-api/references/dashboard-views.md
@@ -35,7 +35,16 @@ results or treat its interpolated values as separately measured operating points
data-dependent choices.
MiniMax M3 has `category=deprecated` in `options.models`; it remains selectable
through explicit model parameters and overview `modelScope=all`. MiniMax M3.1
- Flash remains in the default model group.
+ Flash remains in the default model group. MiniMax M3 inference charts label
+ historical results as “Historical benchmark — submissions closed” beside
+ their source attribution, including PNG exports and unofficial-run overlays.
+ This label is presentation only; view API values and filters are unchanged.
+
+ 中文:MiniMax M3 在 `options.models` 中的分类为 `deprecated`,仍可通过显式模型参数或
+ overview 的 `modelScope=all` 查询;MiniMax M3.1 Flash 仍保留在默认模型组中。MiniMax M3
+ 的推理图表会在来源标注旁显示“历史基准测试(已停止接收提交)”,PNG 导出和非官方 run
+ overlay 中同样保留。该标注仅用于展示,不改变视图 API 的数值或筛选行为。
+
2. Build one public HTTPS GET URL using `URLSearchParams`, especially for JSON
costs, commas, slashes, and run-specific comparison dates. No credentials are
required. Unknown or repeated parameter names return 400.
From 0161a09174fa7aea22825bb68b1bec253c9c3d2f Mon Sep 17 00:00:00 2001
From: Oseltamivir <58582368+Oseltamivir@users.noreply.github.com>
Date: Mon, 26 Oct 2026 14:49:10 +0800
Subject: [PATCH 2/5] fix: render MiniMax M3 status as a plot background
watermark
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Replace the source-caption disclaimer with large translucent SVG text behind the plot. Preserve the watermark through resize, PNG export, and unofficial overlays, and remove it when switching models.
中文:将来源旁的状态提示改为绘图区背景中的大号半透明 SVG 水印。水印支持缩放布局、PNG 导出和非官方 overlay,并在切换模型时移除。
---
.../app/cypress/e2e/inference-chart.cy.ts | 29 ++++++-------
.../components/inference/ui/ChartDisplay.tsx | 9 +---
.../src/components/inference/ui/GPUGraph.tsx | 7 +++-
.../components/inference/ui/PowerTimeline.tsx | 5 ++-
.../components/inference/ui/ScatterGraph.tsx | 2 +
.../src/components/ui/result-context.test.tsx | 19 ---------
.../app/src/components/ui/result-context.tsx | 15 +------
packages/app/src/hooks/useChartExport.test.ts | 31 --------------
.../app/src/lib/catalog/data-mappings.test.ts | 11 +++++
packages/app/src/lib/catalog/data-mappings.ts | 10 +++++
.../app/src/lib/d3-chart/D3Chart/D3Chart.tsx | 2 +
.../app/src/lib/d3-chart/D3Chart/types.ts | 1 +
.../d3-chart/D3Chart/useD3ChartRenderer.ts | 3 ++
.../app/src/lib/d3-chart/chart-setup.test.ts | 41 ++++++++++++++++++
packages/app/src/lib/d3-chart/chart-setup.ts | 4 ++
packages/app/src/lib/d3-chart/types.ts | 1 +
packages/app/src/lib/d3-chart/typography.ts | 2 +
packages/app/src/lib/d3-chart/watermark.ts | 42 +++++++++++++++++++
.../app/src/lib/views-api/docs/options.ts | 4 +-
.../skills/inferencex-api/integrity.json | 2 +-
.../references/dashboard-views.md | 10 ++---
21 files changed, 154 insertions(+), 96 deletions(-)
diff --git a/packages/app/cypress/e2e/inference-chart.cy.ts b/packages/app/cypress/e2e/inference-chart.cy.ts
index a6bf19a03..6a78a4193 100644
--- a/packages/app/cypress/e2e/inference-chart.cy.ts
+++ b/packages/app/cypress/e2e/inference-chart.cy.ts
@@ -155,7 +155,7 @@ describe('Inference Chart', () => {
.and('not.contain.text', 'Workload:')
.and('not.contain.text', 'Precision:')
.and('not.contain.text', 'Metric:');
- cy.get('[data-testid="result-context-source-note"]').should('not.exist');
+ cy.get('[data-testid="historical-watermark"]').should('not.exist');
});
it('plots OpenRouter-priced token revenue for official and unofficial runs', () => {
@@ -390,8 +390,8 @@ describe('Inference Chart', () => {
});
});
-describe('MiniMax M3 historical benchmark attribution', () => {
- it('keeps the status beside the source for official charts, overlays, and the Chinese mobile chart', () => {
+describe('MiniMax M3 historical background watermark', () => {
+ it('keeps a large plot watermark in PNG exports, overlays, and the Chinese mobile chart', () => {
const model = 'minimaxm3';
const officialRows = b300Rows(null).map((row) => ({ ...row, model }));
const overlayRows = b300Rows(OVERLAY_RUN_URL).map((row) => ({ ...row, model }));
@@ -424,15 +424,16 @@ describe('MiniMax M3 historical benchmark attribution', () => {
});
cy.get('#chart-0 [data-testid="result-context"]')
.should('contain.text', 'Source: SemiAnalysis InferenceX™')
- .and('contain.text', 'Historical benchmark — submissions closed');
- cy.get('#chart-0 [data-testid="result-context-source-note"]').should('be.visible');
+ .and('not.contain.text', 'Historical benchmark — submissions closed');
+ cy.get('#chart-0 svg [data-testid="historical-watermark"]').should('be.visible');
let exportedPng = '';
let exportedCaption = '';
cy.window().then((win) => {
win.HTMLAnchorElement.prototype.click = function () {
if (this.download.endsWith('.png')) {
exportedPng = this.href;
- exportedCaption = win.document.querySelector('#chart-0-export')?.textContent ?? '';
+ exportedCaption =
+ win.document.querySelector('#chart-0-export .historical-watermark')?.textContent ?? '';
}
};
});
@@ -441,7 +442,8 @@ describe('MiniMax M3 historical benchmark attribution', () => {
cy.window()
.should(() => expect(exportedPng).to.match(/^data:image\/png;base64,/u))
.then(() => {
- expect(exportedCaption).to.contain('Historical benchmark — submissions closed');
+ expect(exportedCaption).to.contain('HISTORICAL BENCHMARK');
+ expect(exportedCaption).to.contain('SUBMISSIONS CLOSED');
cy.writeFile(
'cypress/downloads/minimax-m3-historical.png',
exportedPng.split(',')[1],
@@ -454,22 +456,21 @@ describe('MiniMax M3 historical benchmark attribution', () => {
});
cy.wait('@historicalM3Overlay');
cy.get('#chart-0 .unofficial-overlay-pt').should('have.length.greaterThan', 0);
- cy.get('#chart-0 [data-testid="result-context-source-note"]').should(
- 'have.text',
- ' · Historical benchmark — submissions closed',
- );
+ cy.get('#chart-0 .historical-watermark').should('contain.text', 'HISTORICAL BENCHMARK');
+ cy.get('#chart-0 .unofficial-watermark-image').should('exist');
cy.get('[aria-label="Dismiss historical-m3-overlay"]').click();
cy.get('#chart-0 .unofficial-overlay-pt').should('not.exist');
- cy.get('#chart-0 [data-testid="result-context-source-note"]').should('be.visible');
+ cy.get('#chart-0 svg [data-testid="historical-watermark"]').should('be.visible');
cy.viewport(375, 900);
cy.visit(`/zh/inference/minimax-m3?i_seq=agentic-traces&unofficialrun=${OVERLAY_RUN_ID}`, {
onBeforeLoad: unlockAgenticGate,
});
cy.wait('@historicalM3Overlay');
- cy.get('#chart-0 [data-testid="result-context-source-note"]')
+ cy.get('#chart-0 svg [data-testid="historical-watermark"]')
.should('be.visible')
- .and('have.text', ' · 历史基准测试(已停止接收提交)');
+ .and('contain.text', '历史基准测试')
+ .and('contain.text', '已停止接收提交');
cy.document().then((doc) => {
expect(doc.documentElement.scrollWidth).to.be.at.most(doc.documentElement.clientWidth);
});
diff --git a/packages/app/src/components/inference/ui/ChartDisplay.tsx b/packages/app/src/components/inference/ui/ChartDisplay.tsx
index d8ffcc72a..ca8ba5ed3 100644
--- a/packages/app/src/components/inference/ui/ChartDisplay.tsx
+++ b/packages/app/src/components/inference/ui/ChartDisplay.tsx
@@ -79,7 +79,7 @@ import {
useUnofficialRun,
} from '@/providers/unofficial-run-provider';
import {
- Model,
+ type Model,
type Precision,
Sequence,
getModelLabel,
@@ -136,7 +136,6 @@ const STRINGS = {
table: 'Table',
sourceUnofficial: 'Source: UNOFFICIAL',
sourceOfficial: 'Source: SemiAnalysis InferenceX™',
- historicalBenchmarkStatus: 'Historical benchmark — submissions closed',
revenuePrices: (input: string, cached: string | null, output: string) =>
cached === null
? `Input $${input}/M tok · Output $${output}/M tok`
@@ -168,7 +167,6 @@ const STRINGS = {
table: '表格',
sourceUnofficial: '来源:非官方',
sourceOfficial: '来源:SemiAnalysis InferenceX™',
- historicalBenchmarkStatus: '历史基准测试(已停止接收提交)',
revenuePrices: (input: string, cached: string | null, output: string) =>
cached === null
? `输入 $${input}/百万 token · 输出 $${output}/百万 token`
@@ -1123,11 +1121,6 @@ export default function ChartDisplay({ embedded = false }: { embedded?: boolean
: 'SemiAnalysis InferenceX™ + UNOFFICIAL overlays'
: 'SemiAnalysis InferenceX™'
}
- sourceNote={
- graph.model === Model.MiniMax_M3
- ? t.historicalBenchmarkStatus
- : undefined
- }
costBasis={
usesTokenSalePricing(selectedYAxisMetric) && tokenRevenuePricing
? t.revenuePrices(
diff --git a/packages/app/src/components/inference/ui/GPUGraph.tsx b/packages/app/src/components/inference/ui/GPUGraph.tsx
index 95a51ad99..182524f3f 100644
--- a/packages/app/src/components/inference/ui/GPUGraph.tsx
+++ b/packages/app/src/components/inference/ui/GPUGraph.tsx
@@ -26,7 +26,11 @@ import {
getInferenceRunLabel,
getOverlayLineLabel,
} from '@/lib/benchmarks/inference-labels';
-import { getChartWatermark, Sequence } from '@/lib/catalog/data-mappings';
+import {
+ getChartWatermark,
+ getHistoricalChartWatermark,
+ Sequence,
+} from '@/lib/catalog/data-mappings';
import { useLocale } from '@/lib/i18n/use-locale';
import { formatNumber, getDisplayLabel, updateRepoUrl } from '@/lib/shared/utils';
import { perfRulerAxisMetricKey, usePerfRulerAxisReset } from '@/hooks/usePerfRulerAxisReset';
@@ -1310,6 +1314,7 @@ const GPUGraph = React.memo(
data={officialPoints}
margin={CHART_MARGIN}
watermark={getChartWatermark()}
+ backgroundWatermark={getHistoricalChartWatermark(modelLabel, locale)}
testId="gpu-graph"
grabCursor={true}
caption={caption}
diff --git a/packages/app/src/components/inference/ui/PowerTimeline.tsx b/packages/app/src/components/inference/ui/PowerTimeline.tsx
index fc41e0d74..69dcf85a9 100644
--- a/packages/app/src/components/inference/ui/PowerTimeline.tsx
+++ b/packages/app/src/components/inference/ui/PowerTimeline.tsx
@@ -29,6 +29,7 @@ import { lttbDownsample } from '@/lib/d3-chart/downsample';
import { CHART_FONT_SANS, CHART_TYPE, px } from '@/lib/d3-chart/typography';
import { overlayRunColor, overlayRunIndex } from '@/lib/charts/overlay-run-style';
import { useLocale } from '@/lib/i18n/use-locale';
+import { getHistoricalChartWatermark } from '@/lib/catalog/data-mappings';
import { getDisplayLabel } from '@/lib/shared/utils';
import {
@@ -628,7 +629,8 @@ export default function PowerTimeline({
const locale = useLocale();
const t = STRINGS[locale];
const { hardwareConfig, hwTypesWithData } = useInferenceData();
- const { activeHwTypes, selectedPrecisions, quickFilters, activeDates } = useInferenceFilters();
+ const { activeHwTypes, selectedPrecisions, quickFilters, activeDates, selectedModel } =
+ useInferenceFilters();
const { isLegendExpanded, highContrast } = useInferenceDisplay();
const { setBestPerSku, toggleHwType, setIsLegendExpanded, toggleActiveDate } =
useInferenceActions();
@@ -1423,6 +1425,7 @@ export default function PowerTimeline({
height={CHART_HEIGHT}
margin={MARGIN}
watermark={overlayData && overlayPoints.length > 0 ? 'unofficial' : 'logo'}
+ backgroundWatermark={getHistoricalChartWatermark(selectedModel, locale)}
testId="power-timeline-chart-svg"
grabCursor
instructions={t.instructions}
diff --git a/packages/app/src/components/inference/ui/ScatterGraph.tsx b/packages/app/src/components/inference/ui/ScatterGraph.tsx
index 1418457f7..c94168dc1 100644
--- a/packages/app/src/components/inference/ui/ScatterGraph.tsx
+++ b/packages/app/src/components/inference/ui/ScatterGraph.tsx
@@ -45,6 +45,7 @@ import {
} from '@/lib/catalog/constants';
import {
getChartWatermark,
+ getHistoricalChartWatermark,
getPrecisionLabel,
getSequenceLabel,
type Precision,
@@ -3723,6 +3724,7 @@ const ScatterGraph = React.memo(
metricIdentity={metricIdentity}
margin={CHART_MARGIN}
watermark={getChartWatermark(isUnofficialRun)}
+ backgroundWatermark={getHistoricalChartWatermark(modelLabel, locale)}
testId="scatter-graph"
grabCursor={true}
caption={caption}
diff --git a/packages/app/src/components/ui/result-context.test.tsx b/packages/app/src/components/ui/result-context.test.tsx
index f434926c0..e60e54b72 100644
--- a/packages/app/src/components/ui/result-context.test.tsx
+++ b/packages/app/src/components/ui/result-context.test.tsx
@@ -71,25 +71,6 @@ describe('ResultContext', () => {
);
});
- it('keeps a historical status beside its source in the visible and exportable caption', () => {
- for (const [locale, status] of [
- ['en', 'Historical benchmark — submissions closed'],
- ['zh', '历史基准测试(已停止接收提交)'],
- ] as const) {
- const container = document.createElement('div');
- act(() => {
- createRoot(container).render(
- ,
- );
- });
- const note = container.querySelector('[data-testid="result-context-source-note"]');
- expect(note?.textContent).toBe(` · ${status}`);
- expect(note?.closest('dd')?.textContent).toBe(`SemiAnalysis InferenceX™ · ${status}`);
- expect(container.cloneNode(true).textContent).toContain(status);
- expect(note?.closest('.no-export')).toBeNull();
- }
- });
-
it('hosts an inline Cost Tier control and keeps a plain-text twin for PNG export', () => {
const container = document.createElement('div');
act(() => {
diff --git a/packages/app/src/components/ui/result-context.tsx b/packages/app/src/components/ui/result-context.tsx
index 250925976..7853ed85f 100644
--- a/packages/app/src/components/ui/result-context.tsx
+++ b/packages/app/src/components/ui/result-context.tsx
@@ -24,7 +24,6 @@ export interface ResultContextProps {
dates?: readonly string[];
dateRange?: { start: string; end: string };
source?: string;
- sourceNote?: string;
costBasis?: string;
costBasisTestId?: string;
}
@@ -84,7 +83,6 @@ export function ResultContext({
dates,
dateRange,
source,
- sourceNote,
costBasis,
costBasisTestId,
}: ResultContextProps) {
@@ -206,18 +204,7 @@ export function ResultContext({
{source && (
{labels.source}:{' '}
-
- {source}
- {sourceNote && (
-
- {' · '}
- {sourceNote}
-
- )}
-
+ {source}
)}
{costBasis && (
diff --git a/packages/app/src/hooks/useChartExport.test.ts b/packages/app/src/hooks/useChartExport.test.ts
index 75545b359..9c882e100 100644
--- a/packages/app/src/hooks/useChartExport.test.ts
+++ b/packages/app/src/hooks/useChartExport.test.ts
@@ -16,7 +16,6 @@ import {
normalizeChartSvgWidthsForExport,
useChartExport,
} from './useChartExport';
-import { ResultContext } from '@/components/ui/result-context';
describe('useChartExport failure messages', () => {
let root: Root;
@@ -90,36 +89,6 @@ describe('useChartExport failure messages', () => {
expect(chart.textContent).toBe('DeepSeek R1');
});
- it('includes the historical source note in the PNG capture clone', async () => {
- const captionRoot = createRoot(chart);
- act(() => {
- captionRoot.render(
- createElement(ResultContext, {
- locale: 'en',
- source: 'SemiAnalysis InferenceX™',
- sourceNote: 'Historical benchmark — submissions closed',
- }),
- );
- });
- let snapshot: HTMLElement | undefined;
- exportMocks.toPng.mockImplementationOnce((element: HTMLElement) => {
- snapshot = element.cloneNode(true) as HTMLElement;
- throw new Error('stop after capture');
- });
- vi.spyOn(window, 'alert').mockImplementation(() => {});
- vi.spyOn(console, 'error').mockImplementation(() => {});
-
- await act(async () => {
- await current.exportToImage();
- });
-
- expect(snapshot?.querySelector('[data-testid="result-context-source-note"]')?.textContent).toBe(
- ' · Historical benchmark — submissions closed',
- );
- expect(snapshot?.textContent).toContain('Source: SemiAnalysis InferenceX™');
- act(() => captionRoot.unmount());
- });
-
it.each([true, false])(
'omits the legend column and keeps official/overlay labels when the sidebar is open=%s',
async (open) => {
diff --git a/packages/app/src/lib/catalog/data-mappings.test.ts b/packages/app/src/lib/catalog/data-mappings.test.ts
index 86509ab62..f1935219e 100644
--- a/packages/app/src/lib/catalog/data-mappings.test.ts
+++ b/packages/app/src/lib/catalog/data-mappings.test.ts
@@ -4,6 +4,7 @@ import {
getModelAndSequence,
getModelAndSequenceFromArtifact,
getModelLabel,
+ getHistoricalChartWatermark,
getOpenRouterModelId,
MODEL_OPTIONS,
SEQUENCE_OPTIONS,
@@ -167,6 +168,16 @@ describe('getModelAndSequenceFromArtifact', () => {
});
describe('isModelDeprecated', () => {
+ it('adds a localized historical watermark only to MiniMax M3', () => {
+ expect(getHistoricalChartWatermark(Model.MiniMax_M3, 'en')).toBe(
+ 'HISTORICAL BENCHMARK\nSUBMISSIONS CLOSED',
+ );
+ expect(getHistoricalChartWatermark(Model.MiniMax_M3, 'zh')).toBe(
+ '历史基准测试\n已停止接收提交',
+ );
+ expect(getHistoricalChartWatermark(Model.MiniMax_M3_1_Flash, 'en')).toBeUndefined();
+ expect(getHistoricalChartWatermark(Model.DeepSeek_R1, 'en')).toBeUndefined();
+ });
it('returns true for deprecated model Llama3_3_70B', () => {
expect(isModelDeprecated(Model.Llama3_3_70B)).toBe(true);
});
diff --git a/packages/app/src/lib/catalog/data-mappings.ts b/packages/app/src/lib/catalog/data-mappings.ts
index b6530f06d..f2e34c450 100644
--- a/packages/app/src/lib/catalog/data-mappings.ts
+++ b/packages/app/src/lib/catalog/data-mappings.ts
@@ -258,6 +258,16 @@ export function getChartWatermark(isUnofficialRun = false): 'logo' | 'unofficial
return isUnofficialRun ? 'unofficial' : 'logo';
}
+export function getHistoricalChartWatermark(
+ model: string | undefined,
+ locale: 'en' | 'zh',
+): string | undefined {
+ if (model !== Model.MiniMax_M3) return undefined;
+ return locale === 'zh'
+ ? '历史基准测试\n已停止接收提交'
+ : 'HISTORICAL BENCHMARK\nSUBMISSIONS CLOSED';
+}
+
export const MODEL_PREFIX_MAPPING: Record = Object.fromEntries(
(Object.entries(MODEL_CONFIG) as [Model, (typeof MODEL_CONFIG)[Model]][])
.filter(([, c]) => c.prefix)
diff --git a/packages/app/src/lib/d3-chart/D3Chart/D3Chart.tsx b/packages/app/src/lib/d3-chart/D3Chart/D3Chart.tsx
index 4a466736d..a6ebf28a1 100644
--- a/packages/app/src/lib/d3-chart/D3Chart/D3Chart.tsx
+++ b/packages/app/src/lib/d3-chart/D3Chart/D3Chart.tsx
@@ -21,6 +21,7 @@ function D3ChartInner(
height = 600,
margin,
watermark,
+ backgroundWatermark,
testId,
grabCursor = true,
instructions,
@@ -107,6 +108,7 @@ function D3ChartInner(
height,
margin,
watermark,
+ backgroundWatermark,
testId,
grabCursor,
instructions,
diff --git a/packages/app/src/lib/d3-chart/D3Chart/types.ts b/packages/app/src/lib/d3-chart/D3Chart/types.ts
index 775cf2ef8..027fdcd11 100644
--- a/packages/app/src/lib/d3-chart/D3Chart/types.ts
+++ b/packages/app/src/lib/d3-chart/D3Chart/types.ts
@@ -195,6 +195,7 @@ export interface D3ChartProps {
height?: number;
margin?: ChartMargin;
watermark?: 'logo' | 'unofficial' | 'none';
+ backgroundWatermark?: string;
testId?: string;
grabCursor?: boolean;
instructions?: string;
diff --git a/packages/app/src/lib/d3-chart/D3Chart/useD3ChartRenderer.ts b/packages/app/src/lib/d3-chart/D3Chart/useD3ChartRenderer.ts
index b50cf7d21..f84970f33 100644
--- a/packages/app/src/lib/d3-chart/D3Chart/useD3ChartRenderer.ts
+++ b/packages/app/src/lib/d3-chart/D3Chart/useD3ChartRenderer.ts
@@ -232,6 +232,7 @@ export function useD3ChartRenderer(props: D3ChartProps, deps: RendererDeps
data,
margin = { top: 24, right: 10, bottom: 40, left: 60 },
watermark = 'logo',
+ backgroundWatermark,
clipContent = true,
dataIdentity,
metricIdentity,
@@ -317,6 +318,7 @@ export function useD3ChartRenderer(props: D3ChartProps, deps: RendererDeps
containerHeight: dimensions.height,
margin,
watermark: getDomainAwareChartWatermark(watermark, window.location.hostname),
+ backgroundWatermark,
xLabel: xAxisConfig?.label,
yLabel: yAxisConfig?.label,
clipContent,
@@ -331,6 +333,7 @@ export function useD3ChartRenderer(props: D3ChartProps, deps: RendererDeps
margin.bottom,
margin.left,
watermark,
+ backgroundWatermark,
xAxisConfig?.label,
yAxisConfig?.label,
clipContent,
diff --git a/packages/app/src/lib/d3-chart/chart-setup.test.ts b/packages/app/src/lib/d3-chart/chart-setup.test.ts
index faf5682f3..63a062e2d 100644
--- a/packages/app/src/lib/d3-chart/chart-setup.test.ts
+++ b/packages/app/src/lib/d3-chart/chart-setup.test.ts
@@ -186,6 +186,47 @@ describe('setupChartStructure', () => {
});
describe('watermark variants', () => {
+ it('keeps a large historical watermark behind the plot alongside unofficial marks through resize and removal', () => {
+ const svgEl = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ setupChartStructure(
+ svgEl,
+ defaultConfig({
+ watermark: 'unofficial',
+ backgroundWatermark: 'HISTORICAL BENCHMARK\nSUBMISSIONS CLOSED',
+ }),
+ );
+ const mark = svgEl.querySelector('.historical-watermark')!;
+ expect(mark.parentElement).toBe(svgEl);
+ expect([...svgEl.children].indexOf(mark)).toBeLessThan(
+ [...svgEl.children].indexOf(svgEl.querySelector('.chart-root')!),
+ );
+ expect(mark.getAttribute('viewBox')).toBe('0 0 1000 500');
+ expect(mark.getAttribute('pointer-events')).toBe('none');
+ expect([...mark.querySelectorAll('text')].map((node) => node.textContent)).toEqual([
+ 'HISTORICAL BENCHMARK',
+ 'SUBMISSIONS CLOSED',
+ ]);
+ expect(svgEl.querySelector('.unofficial-watermark-image')).not.toBeNull();
+
+ const resized = defaultConfig({
+ watermark: 'unofficial',
+ backgroundWatermark: '历史基准测试\n已停止接收提交',
+ containerWidth: 400,
+ });
+ setupChartStructure(svgEl, resized);
+ expect(svgEl.querySelectorAll('.historical-watermark')).toHaveLength(1);
+ expect(svgEl.querySelector('.historical-watermark')?.getAttribute('width')).toBe(
+ String(400 - resized.margin.left - resized.margin.right),
+ );
+ expect(svgEl.querySelector('.historical-watermark')?.textContent).toBe(
+ '历史基准测试已停止接收提交',
+ );
+
+ setupChartStructure(svgEl, defaultConfig({ watermark: 'logo' }));
+ expect(svgEl.querySelector('.historical-watermark')).toBeNull();
+ expect(svgEl.querySelector('.unofficial-watermark-image')).toBeNull();
+ expect(svgEl.querySelector('.watermark-rect')).not.toBeNull();
+ });
it('creates logo watermark when watermark is "logo"', () => {
const svgEl = makeSvgEl();
const config = defaultConfig({ watermark: 'logo' });
diff --git a/packages/app/src/lib/d3-chart/chart-setup.ts b/packages/app/src/lib/d3-chart/chart-setup.ts
index da556206a..4c3fbda15 100644
--- a/packages/app/src/lib/d3-chart/chart-setup.ts
+++ b/packages/app/src/lib/d3-chart/chart-setup.ts
@@ -6,6 +6,7 @@ import {
createLogoWatermark,
createUnofficialWatermark,
positionUnofficialWatermarkImage,
+ updateBackgroundWatermark,
} from './watermark';
export function setupChartStructure(
@@ -119,6 +120,7 @@ export function setupChartStructure(
.text(config.xLabel);
}
+ updateBackgroundWatermark(svg, config.backgroundWatermark, width, height, margin);
return { svg, g, zoomGroup, xAxisGroup, yAxisGroup, gridGroup, defs, width, height, margin };
}
@@ -195,6 +197,8 @@ export function setupChartStructure(
}
}
+ updateBackgroundWatermark(svg, config.backgroundWatermark, width, height, margin);
+
const yLabel = svg.select('.y-axis-label');
if (!yLabel.empty()) {
yLabel.attr('x', -(margin.top + height / 2)).text(config.yLabel ?? '');
diff --git a/packages/app/src/lib/d3-chart/types.ts b/packages/app/src/lib/d3-chart/types.ts
index a9cc10e9c..caa53d592 100644
--- a/packages/app/src/lib/d3-chart/types.ts
+++ b/packages/app/src/lib/d3-chart/types.ts
@@ -26,6 +26,7 @@ export interface ChartSetupConfig {
containerHeight: number;
margin: ChartMargin;
watermark: 'logo' | 'unofficial' | 'none';
+ backgroundWatermark?: string;
xLabel?: string;
yLabel?: string;
clipContent?: boolean;
diff --git a/packages/app/src/lib/d3-chart/typography.ts b/packages/app/src/lib/d3-chart/typography.ts
index d44c5a60f..7d22bebd0 100644
--- a/packages/app/src/lib/d3-chart/typography.ts
+++ b/packages/app/src/lib/d3-chart/typography.ts
@@ -7,6 +7,8 @@ export const CHART_TYPE = {
lineLabel: 13,
micro: 9,
watermark: 20,
+ backgroundWatermarkTitle: 72,
+ backgroundWatermarkSubtitle: 40,
} as const;
export function px(size: number): string {
diff --git a/packages/app/src/lib/d3-chart/watermark.ts b/packages/app/src/lib/d3-chart/watermark.ts
index f08f15914..678b2c549 100644
--- a/packages/app/src/lib/d3-chart/watermark.ts
+++ b/packages/app/src/lib/d3-chart/watermark.ts
@@ -5,6 +5,48 @@ import { CHART_TYPE, px } from './typography';
const UNOFFICIAL_WATERMARK_IMAGE_PATH = '/decorative/kanye-west.png';
+export function updateBackgroundWatermark(
+ svg: d3.Selection,
+ label: string | undefined,
+ innerWidth: number,
+ innerHeight: number,
+ margin: ChartMargin,
+): void {
+ svg.select('.historical-watermark').remove();
+ if (!label) return;
+
+ const mark = svg
+ .insert('svg', '.chart-root')
+ .attr('class', 'historical-watermark')
+ .attr('data-testid', 'historical-watermark')
+ .attr('x', margin.left)
+ .attr('y', margin.top)
+ .attr('width', innerWidth)
+ .attr('height', innerHeight)
+ .attr('viewBox', '0 0 1000 500')
+ .attr('preserveAspectRatio', 'xMidYMid meet')
+ .attr('fill', 'currentColor')
+ .attr('opacity', 0.16)
+ .attr('pointer-events', 'none')
+ .attr('role', 'img')
+ .attr('aria-label', label.replace('\n', ' — '));
+
+ mark
+ .selectAll('text')
+ .data(label.split('\n'))
+ .join('text')
+ .attr('x', 500)
+ .attr('y', (_, index) => 230 + index * 80)
+ .attr('text-anchor', 'middle')
+ .attr('font-size', (_, index) =>
+ px(
+ index === 0 ? CHART_TYPE.backgroundWatermarkTitle : CHART_TYPE.backgroundWatermarkSubtitle,
+ ),
+ )
+ .attr('font-weight', 800)
+ .text((line) => line);
+}
+
function insertWatermarkRect(
svg: d3.Selection,
innerWidth: number,
diff --git a/packages/app/src/lib/views-api/docs/options.ts b/packages/app/src/lib/views-api/docs/options.ts
index 1c7e4e0b9..f58ee4c17 100644
--- a/packages/app/src/lib/views-api/docs/options.ts
+++ b/packages/app/src/lib/views-api/docs/options.ts
@@ -239,8 +239,8 @@ export const operations: ApiOperation[] = [
path: '/api/v1/views/options',
summary: text('Discover every views-API option domain', '获取 views API 的全部选项域'),
description: text(
- 'Static discovery endpoint for the views API: models (with DB keys, category and release date), sequences, precisions, hardware (with vendor, architecture, TDP and per-provider hourly cost), frameworks, speculative-decoding methods, percentiles, x-axis and scale modes, chart metrics (bilingual labels, unit, polarity), quick-filter domains, reliability ranges, and overview/calculator/fleet option sets — plus the defaults the dashboard itself uses. Values come from the same registries the dashboard renders its controls from, so this response is the authoritative input catalog for the other /api/v1/views endpoints. MiniMax M3 has category deprecated; its historical-submissions-closed chart label is presentation only and does not change view data. No database access.',
- 'views API 的静态发现端点:模型(含数据库键、分类与发布日期)、序列、精度、硬件(含厂商、架构、TDP 及各供应商每小时成本)、推理框架、投机解码方法、百分位、x 轴与坐标缩放模式、图表指标(中英文标签、单位、极性)、快捷筛选域、可靠性时间范围,以及 overview/calculator/fleet 的选项集,并附仪表盘实际使用的默认值。所有取值直接来自仪表盘控件渲染所用的注册表,因此该响应是其余 /api/v1/views 端点的权威输入目录。MiniMax M3 的分类为 deprecated;图表中“历史基准测试(已停止接收提交)”的标注仅用于展示,不改变视图数据。不访问数据库。',
+ 'Static discovery endpoint for the views API: models (with DB keys, category and release date), sequences, precisions, hardware (with vendor, architecture, TDP and per-provider hourly cost), frameworks, speculative-decoding methods, percentiles, x-axis and scale modes, chart metrics (bilingual labels, unit, polarity), quick-filter domains, reliability ranges, and overview/calculator/fleet option sets — plus the defaults the dashboard itself uses. Values come from the same registries the dashboard renders its controls from, so this response is the authoritative input catalog for the other /api/v1/views endpoints. MiniMax M3 has category deprecated; its historical benchmark background watermark is presentation only and does not change view data. No database access.',
+ 'views API 的静态发现端点:模型(含数据库键、分类与发布日期)、序列、精度、硬件(含厂商、架构、TDP 及各供应商每小时成本)、推理框架、投机解码方法、百分位、x 轴与坐标缩放模式、图表指标(中英文标签、单位、极性)、快捷筛选域、可靠性时间范围,以及 overview/calculator/fleet 的选项集,并附仪表盘实际使用的默认值。所有取值直接来自仪表盘控件渲染所用的注册表,因此该响应是其余 /api/v1/views 端点的权威输入目录。MiniMax M3 的分类为 deprecated;图表背景中的“历史基准测试 / 已停止接收提交”水印仅用于展示,不改变视图数据。不访问数据库。',
),
audience: 'public',
stability: 'beta',
diff --git a/packages/skills/skills/inferencex-api/integrity.json b/packages/skills/skills/inferencex-api/integrity.json
index f1803e33a..256fea3b2 100644
--- a/packages/skills/skills/inferencex-api/integrity.json
+++ b/packages/skills/skills/inferencex-api/integrity.json
@@ -9,7 +9,7 @@
"references/cli-contract.md": "4f070877eeefffad2860c58f79f68432fbe27aa21fc578a1c696f0309a3d7d7b",
"references/cli.md": "11e1183f7ed70d669019e8435ac16a6a9f873de027507b8850a9943f6659bfd9",
"references/collectivex.md": "f79cf8e2779a9289ab69f79f4a4b338e0b9a5f76a3750a3e969f33a744e726a1",
- "references/dashboard-views.md": "928ed7a1a1b44006ad5ebf12952560eb1e5459cd85257cabffa7e8cc2dd5fe3a",
+ "references/dashboard-views.md": "baa524758d8ef683c0c0a0acbc6229d3696be871ac733bf5a553defc0f7a9f98",
"references/offline-exports.md": "95aa565dcd4c9e592159baa560a9a58217bc61e395de1c6daf0576795c7f4ec9",
"references/pareto.md": "1b4d2d163f982e3f2d97addce310789eae9c1e5badd82dd7501708c9e0385027",
"references/powerx.md": "5af5527d54a7e6191a5769080c122638b333b1ceb59e19fdee7df662a5cd3c8f",
diff --git a/packages/skills/skills/inferencex-api/references/dashboard-views.md b/packages/skills/skills/inferencex-api/references/dashboard-views.md
index 41df9edf6..4b3d201dd 100644
--- a/packages/skills/skills/inferencex-api/references/dashboard-views.md
+++ b/packages/skills/skills/inferencex-api/references/dashboard-views.md
@@ -36,14 +36,14 @@ results or treat its interpolated values as separately measured operating points
MiniMax M3 has `category=deprecated` in `options.models`; it remains selectable
through explicit model parameters and overview `modelScope=all`. MiniMax M3.1
Flash remains in the default model group. MiniMax M3 inference charts label
- historical results as “Historical benchmark — submissions closed” beside
- their source attribution, including PNG exports and unofficial-run overlays.
- This label is presentation only; view API values and filters are unchanged.
+ historical results with a large “HISTORICAL BENCHMARK / SUBMISSIONS CLOSED”
+ watermark behind the plot, including PNG exports and unofficial-run overlays.
+ This watermark is presentation only; view API values and filters are unchanged.
中文:MiniMax M3 在 `options.models` 中的分类为 `deprecated`,仍可通过显式模型参数或
overview 的 `modelScope=all` 查询;MiniMax M3.1 Flash 仍保留在默认模型组中。MiniMax M3
- 的推理图表会在来源标注旁显示“历史基准测试(已停止接收提交)”,PNG 导出和非官方 run
- overlay 中同样保留。该标注仅用于展示,不改变视图 API 的数值或筛选行为。
+ 的推理图表会在绘图区背景中显示大号“历史基准测试 / 已停止接收提交”水印,PNG 导出和非官方
+ run overlay 中同样保留。该水印仅用于展示,不改变视图 API 的数值或筛选行为。
2. Build one public HTTPS GET URL using `URLSearchParams`, especially for JSON
costs, commas, slashes, and run-specific comparison dates. No credentials are
From 2365c047e6576c53aa1c6ed59148f3885f0ed65d Mon Sep 17 00:00:00 2001
From: Oseltamivir <58582368+Oseltamivir@users.noreply.github.com>
Date: Mon, 26 Oct 2026 14:52:12 +0800
Subject: [PATCH 3/5] fix: normalize all watermark accessibility line breaks
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
中文:转换水印无障碍标签中的全部换行符。
---
packages/app/src/lib/d3-chart/watermark.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/app/src/lib/d3-chart/watermark.ts b/packages/app/src/lib/d3-chart/watermark.ts
index 678b2c549..df68fd8f3 100644
--- a/packages/app/src/lib/d3-chart/watermark.ts
+++ b/packages/app/src/lib/d3-chart/watermark.ts
@@ -29,7 +29,7 @@ export function updateBackgroundWatermark(
.attr('opacity', 0.16)
.attr('pointer-events', 'none')
.attr('role', 'img')
- .attr('aria-label', label.replace('\n', ' — '));
+ .attr('aria-label', label.replaceAll('\n', ' — '));
mark
.selectAll('text')
From 7e708611913a9c3813405cc3f2cb3f35fae13306 Mon Sep 17 00:00:00 2001
From: Oseltamivir <58582368+Oseltamivir@users.noreply.github.com>
Date: Mon, 26 Oct 2026 15:18:00 +0800
Subject: [PATCH 4/5] fix: watermark every deprecated model below the
attribution logo
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Use the registry deprecated category and the label Deprecated / 已弃用. Keep the SemiAnalysis watermark identical to main and reserve a separate lower plot band so the marks cannot overlap. Cover all deprecated models and verify official-host PNG rendering.
中文:根据注册表中的弃用分类,为所有已弃用模型显示 Deprecated / 已弃用水印。SemiAnalysis 标志与 main 保持一致,新水印位于绘图区下方的独立区域,避免两者重叠。测试覆盖全部已弃用模型,并验证正式域名下的 PNG 渲染。
---
.../app/cypress/e2e/inference-chart.cy.ts | 50 ++++++++++++++-----
.../src/components/inference/ui/GPUGraph.tsx | 4 +-
.../components/inference/ui/PowerTimeline.tsx | 4 +-
.../components/inference/ui/ScatterGraph.tsx | 4 +-
.../app/src/lib/catalog/data-mappings.test.ts | 31 ++++++++----
packages/app/src/lib/catalog/data-mappings.ts | 8 ++-
.../app/src/lib/d3-chart/chart-setup.test.ts | 50 +++++++++++++------
packages/app/src/lib/d3-chart/typography.ts | 3 +-
packages/app/src/lib/d3-chart/watermark.ts | 35 ++++++-------
.../app/src/lib/views-api/docs/options.ts | 4 +-
.../skills/inferencex-api/integrity.json | 2 +-
.../references/dashboard-views.md | 24 +++++----
12 files changed, 137 insertions(+), 82 deletions(-)
diff --git a/packages/app/cypress/e2e/inference-chart.cy.ts b/packages/app/cypress/e2e/inference-chart.cy.ts
index 6a78a4193..45d7df45c 100644
--- a/packages/app/cypress/e2e/inference-chart.cy.ts
+++ b/packages/app/cypress/e2e/inference-chart.cy.ts
@@ -155,7 +155,7 @@ describe('Inference Chart', () => {
.and('not.contain.text', 'Workload:')
.and('not.contain.text', 'Precision:')
.and('not.contain.text', 'Metric:');
- cy.get('[data-testid="historical-watermark"]').should('not.exist');
+ cy.get('[data-testid="deprecated-watermark"]').should('not.exist');
});
it('plots OpenRouter-priced token revenue for official and unofficial runs', () => {
@@ -390,7 +390,35 @@ describe('Inference Chart', () => {
});
});
-describe('MiniMax M3 historical background watermark', () => {
+describe('Deprecated model background watermark', () => {
+ it('marks every deprecated model and clears the mark for an active model', () => {
+ cy.viewport(1280, 900);
+ for (const [display, dbKey, deprecated] of [
+ ['MiniMax-M3', 'minimaxm3', true],
+ ['MiniMax-M2.5', 'minimaxm2.5', true],
+ ['Kimi-K2.5', 'kimik2.5', true],
+ ['GLM-5', 'glm5', true],
+ ['gpt-oss-120b', 'gptoss120b', true],
+ ['Llama-3.3-70B-Instruct-FP8', 'llama70b', true],
+ ['MiniMax-M3.1-Flash', 'minimaxm31flash', false],
+ ] as const) {
+ const rows = b300Rows(null).map((row) => ({ ...row, model: dbKey }));
+ cy.intercept('GET', '/api/v1/availability', {
+ body: rows.map(({ metrics: _metrics, ...row }) => row),
+ });
+ cy.intercept('GET', '/api/v1/benchmarks*', { body: rows });
+ cy.visit(`/inference?g_model=${display}&i_seq=agentic-traces`, {
+ onBeforeLoad: unlockAgenticGate,
+ });
+ cy.get('#chart-0 svg[data-testid="d3-chart-svg"]').should('be.visible');
+ if (deprecated) {
+ cy.get('#chart-0 .deprecated-watermark').should('have.text', 'Deprecated');
+ } else {
+ cy.get('#chart-0 .deprecated-watermark').should('not.exist');
+ }
+ }
+ });
+
it('keeps a large plot watermark in PNG exports, overlays, and the Chinese mobile chart', () => {
const model = 'minimaxm3';
const officialRows = b300Rows(null).map((row) => ({ ...row, model }));
@@ -425,7 +453,7 @@ describe('MiniMax M3 historical background watermark', () => {
cy.get('#chart-0 [data-testid="result-context"]')
.should('contain.text', 'Source: SemiAnalysis InferenceX™')
.and('not.contain.text', 'Historical benchmark — submissions closed');
- cy.get('#chart-0 svg [data-testid="historical-watermark"]').should('be.visible');
+ cy.get('#chart-0 svg [data-testid="deprecated-watermark"]').should('be.visible');
let exportedPng = '';
let exportedCaption = '';
cy.window().then((win) => {
@@ -433,7 +461,7 @@ describe('MiniMax M3 historical background watermark', () => {
if (this.download.endsWith('.png')) {
exportedPng = this.href;
exportedCaption =
- win.document.querySelector('#chart-0-export .historical-watermark')?.textContent ?? '';
+ win.document.querySelector('#chart-0-export .deprecated-watermark')?.textContent ?? '';
}
};
});
@@ -442,10 +470,9 @@ describe('MiniMax M3 historical background watermark', () => {
cy.window()
.should(() => expect(exportedPng).to.match(/^data:image\/png;base64,/u))
.then(() => {
- expect(exportedCaption).to.contain('HISTORICAL BENCHMARK');
- expect(exportedCaption).to.contain('SUBMISSIONS CLOSED');
+ expect(exportedCaption).to.contain('Deprecated');
cy.writeFile(
- 'cypress/downloads/minimax-m3-historical.png',
+ 'cypress/downloads/deprecated-model-watermark.png',
exportedPng.split(',')[1],
'base64',
);
@@ -456,21 +483,20 @@ describe('MiniMax M3 historical background watermark', () => {
});
cy.wait('@historicalM3Overlay');
cy.get('#chart-0 .unofficial-overlay-pt').should('have.length.greaterThan', 0);
- cy.get('#chart-0 .historical-watermark').should('contain.text', 'HISTORICAL BENCHMARK');
+ cy.get('#chart-0 .deprecated-watermark').should('contain.text', 'Deprecated');
cy.get('#chart-0 .unofficial-watermark-image').should('exist');
cy.get('[aria-label="Dismiss historical-m3-overlay"]').click();
cy.get('#chart-0 .unofficial-overlay-pt').should('not.exist');
- cy.get('#chart-0 svg [data-testid="historical-watermark"]').should('be.visible');
+ cy.get('#chart-0 svg [data-testid="deprecated-watermark"]').should('be.visible');
cy.viewport(375, 900);
cy.visit(`/zh/inference/minimax-m3?i_seq=agentic-traces&unofficialrun=${OVERLAY_RUN_ID}`, {
onBeforeLoad: unlockAgenticGate,
});
cy.wait('@historicalM3Overlay');
- cy.get('#chart-0 svg [data-testid="historical-watermark"]')
+ cy.get('#chart-0 svg [data-testid="deprecated-watermark"]')
.should('be.visible')
- .and('contain.text', '历史基准测试')
- .and('contain.text', '已停止接收提交');
+ .and('have.text', '已弃用');
cy.document().then((doc) => {
expect(doc.documentElement.scrollWidth).to.be.at.most(doc.documentElement.clientWidth);
});
diff --git a/packages/app/src/components/inference/ui/GPUGraph.tsx b/packages/app/src/components/inference/ui/GPUGraph.tsx
index 182524f3f..4cf29f835 100644
--- a/packages/app/src/components/inference/ui/GPUGraph.tsx
+++ b/packages/app/src/components/inference/ui/GPUGraph.tsx
@@ -28,7 +28,7 @@ import {
} from '@/lib/benchmarks/inference-labels';
import {
getChartWatermark,
- getHistoricalChartWatermark,
+ getDeprecatedChartWatermark,
Sequence,
} from '@/lib/catalog/data-mappings';
import { useLocale } from '@/lib/i18n/use-locale';
@@ -1314,7 +1314,7 @@ const GPUGraph = React.memo(
data={officialPoints}
margin={CHART_MARGIN}
watermark={getChartWatermark()}
- backgroundWatermark={getHistoricalChartWatermark(modelLabel, locale)}
+ backgroundWatermark={getDeprecatedChartWatermark(modelLabel, locale)}
testId="gpu-graph"
grabCursor={true}
caption={caption}
diff --git a/packages/app/src/components/inference/ui/PowerTimeline.tsx b/packages/app/src/components/inference/ui/PowerTimeline.tsx
index 69dcf85a9..d228b7bc7 100644
--- a/packages/app/src/components/inference/ui/PowerTimeline.tsx
+++ b/packages/app/src/components/inference/ui/PowerTimeline.tsx
@@ -29,7 +29,7 @@ import { lttbDownsample } from '@/lib/d3-chart/downsample';
import { CHART_FONT_SANS, CHART_TYPE, px } from '@/lib/d3-chart/typography';
import { overlayRunColor, overlayRunIndex } from '@/lib/charts/overlay-run-style';
import { useLocale } from '@/lib/i18n/use-locale';
-import { getHistoricalChartWatermark } from '@/lib/catalog/data-mappings';
+import { getDeprecatedChartWatermark } from '@/lib/catalog/data-mappings';
import { getDisplayLabel } from '@/lib/shared/utils';
import {
@@ -1425,7 +1425,7 @@ export default function PowerTimeline({
height={CHART_HEIGHT}
margin={MARGIN}
watermark={overlayData && overlayPoints.length > 0 ? 'unofficial' : 'logo'}
- backgroundWatermark={getHistoricalChartWatermark(selectedModel, locale)}
+ backgroundWatermark={getDeprecatedChartWatermark(selectedModel, locale)}
testId="power-timeline-chart-svg"
grabCursor
instructions={t.instructions}
diff --git a/packages/app/src/components/inference/ui/ScatterGraph.tsx b/packages/app/src/components/inference/ui/ScatterGraph.tsx
index c94168dc1..1bec27b01 100644
--- a/packages/app/src/components/inference/ui/ScatterGraph.tsx
+++ b/packages/app/src/components/inference/ui/ScatterGraph.tsx
@@ -45,7 +45,7 @@ import {
} from '@/lib/catalog/constants';
import {
getChartWatermark,
- getHistoricalChartWatermark,
+ getDeprecatedChartWatermark,
getPrecisionLabel,
getSequenceLabel,
type Precision,
@@ -3724,7 +3724,7 @@ const ScatterGraph = React.memo(
metricIdentity={metricIdentity}
margin={CHART_MARGIN}
watermark={getChartWatermark(isUnofficialRun)}
- backgroundWatermark={getHistoricalChartWatermark(modelLabel, locale)}
+ backgroundWatermark={getDeprecatedChartWatermark(modelLabel, locale)}
testId="scatter-graph"
grabCursor={true}
caption={caption}
diff --git a/packages/app/src/lib/catalog/data-mappings.test.ts b/packages/app/src/lib/catalog/data-mappings.test.ts
index f1935219e..1599ab5dd 100644
--- a/packages/app/src/lib/catalog/data-mappings.test.ts
+++ b/packages/app/src/lib/catalog/data-mappings.test.ts
@@ -4,7 +4,7 @@ import {
getModelAndSequence,
getModelAndSequenceFromArtifact,
getModelLabel,
- getHistoricalChartWatermark,
+ getDeprecatedChartWatermark,
getOpenRouterModelId,
MODEL_OPTIONS,
SEQUENCE_OPTIONS,
@@ -168,15 +168,26 @@ describe('getModelAndSequenceFromArtifact', () => {
});
describe('isModelDeprecated', () => {
- it('adds a localized historical watermark only to MiniMax M3', () => {
- expect(getHistoricalChartWatermark(Model.MiniMax_M3, 'en')).toBe(
- 'HISTORICAL BENCHMARK\nSUBMISSIONS CLOSED',
- );
- expect(getHistoricalChartWatermark(Model.MiniMax_M3, 'zh')).toBe(
- '历史基准测试\n已停止接收提交',
- );
- expect(getHistoricalChartWatermark(Model.MiniMax_M3_1_Flash, 'en')).toBeUndefined();
- expect(getHistoricalChartWatermark(Model.DeepSeek_R1, 'en')).toBeUndefined();
+ it.each([
+ Model.MiniMax_M3,
+ Model.MiniMax_M2_5,
+ Model.Kimi_K2_5,
+ Model.GLM_5,
+ Model.GptOss,
+ Model.Llama3_3_70B,
+ ])('adds a localized Deprecated watermark to %s', (model) => {
+ expect(getDeprecatedChartWatermark(model, 'en')).toBe('Deprecated');
+ expect(getDeprecatedChartWatermark(model, 'zh')).toBe('已弃用');
+ });
+
+ it.each([
+ Model.MiniMax_M3_1_Flash,
+ Model.DeepSeek_R1,
+ Model.Qwen3_8_27B,
+ Model.Llama3_1_70B,
+ undefined,
+ ])('does not mark the non-deprecated model %s', (model) => {
+ expect(getDeprecatedChartWatermark(model, 'en')).toBeUndefined();
});
it('returns true for deprecated model Llama3_3_70B', () => {
expect(isModelDeprecated(Model.Llama3_3_70B)).toBe(true);
diff --git a/packages/app/src/lib/catalog/data-mappings.ts b/packages/app/src/lib/catalog/data-mappings.ts
index f2e34c450..f8f69131d 100644
--- a/packages/app/src/lib/catalog/data-mappings.ts
+++ b/packages/app/src/lib/catalog/data-mappings.ts
@@ -258,14 +258,12 @@ export function getChartWatermark(isUnofficialRun = false): 'logo' | 'unofficial
return isUnofficialRun ? 'unofficial' : 'logo';
}
-export function getHistoricalChartWatermark(
+export function getDeprecatedChartWatermark(
model: string | undefined,
locale: 'en' | 'zh',
): string | undefined {
- if (model !== Model.MiniMax_M3) return undefined;
- return locale === 'zh'
- ? '历史基准测试\n已停止接收提交'
- : 'HISTORICAL BENCHMARK\nSUBMISSIONS CLOSED';
+ if (!model || !isModelDeprecated(model as Model)) return undefined;
+ return locale === 'zh' ? '已弃用' : 'Deprecated';
}
export const MODEL_PREFIX_MAPPING: Record = Object.fromEntries(
diff --git a/packages/app/src/lib/d3-chart/chart-setup.test.ts b/packages/app/src/lib/d3-chart/chart-setup.test.ts
index 63a062e2d..1027c51d7 100644
--- a/packages/app/src/lib/d3-chart/chart-setup.test.ts
+++ b/packages/app/src/lib/d3-chart/chart-setup.test.ts
@@ -186,44 +186,66 @@ describe('setupChartStructure', () => {
});
describe('watermark variants', () => {
- it('keeps a large historical watermark behind the plot alongside unofficial marks through resize and removal', () => {
+ it.each([
+ [400, 600],
+ [1200, 600],
+ [800, 300],
+ ])(
+ 'preserves the SemiAnalysis logo and places Deprecated below it at %s×%s',
+ (containerWidth, containerHeight) => {
+ const svgEl = makeSvgEl();
+ const config = defaultConfig({ watermark: 'logo', containerWidth, containerHeight });
+ setupChartStructure(svgEl, config);
+ const originalLogo = svgEl.querySelector('#logo-pattern-test-chart')!.outerHTML;
+ const originalRect = svgEl.querySelector('.watermark-rect')!.outerHTML;
+
+ setupChartStructure(svgEl, { ...config, backgroundWatermark: 'Deprecated' });
+ expect(svgEl.querySelector('#logo-pattern-test-chart')!.outerHTML).toBe(originalLogo);
+ expect(svgEl.querySelector('.watermark-rect')!.outerHTML).toBe(originalRect);
+ const logo = svgEl.querySelector('#logo-pattern-test-chart image')!;
+ const status = svgEl.querySelector('.deprecated-watermark')!;
+ const logoBottom = Number(logo.getAttribute('y')) + Number(logo.getAttribute('height'));
+ expect(Number(status.getAttribute('y'))).toBeGreaterThan(logoBottom);
+ expect(
+ Number(status.getAttribute('y')) + Number(status.getAttribute('height')),
+ ).toBeLessThanOrEqual(containerHeight - config.margin.bottom);
+ },
+ );
+ it('keeps a large deprecated watermark behind the plot alongside unofficial marks through resize and removal', () => {
const svgEl = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
setupChartStructure(
svgEl,
defaultConfig({
watermark: 'unofficial',
- backgroundWatermark: 'HISTORICAL BENCHMARK\nSUBMISSIONS CLOSED',
+ backgroundWatermark: 'Deprecated',
}),
);
- const mark = svgEl.querySelector('.historical-watermark')!;
+ const mark = svgEl.querySelector('.deprecated-watermark')!;
expect(mark.parentElement).toBe(svgEl);
expect([...svgEl.children].indexOf(mark)).toBeLessThan(
[...svgEl.children].indexOf(svgEl.querySelector('.chart-root')!),
);
- expect(mark.getAttribute('viewBox')).toBe('0 0 1000 500');
+ expect(mark.getAttribute('viewBox')).toBe('0 0 1000 200');
expect(mark.getAttribute('pointer-events')).toBe('none');
expect([...mark.querySelectorAll('text')].map((node) => node.textContent)).toEqual([
- 'HISTORICAL BENCHMARK',
- 'SUBMISSIONS CLOSED',
+ 'Deprecated',
]);
expect(svgEl.querySelector('.unofficial-watermark-image')).not.toBeNull();
const resized = defaultConfig({
watermark: 'unofficial',
- backgroundWatermark: '历史基准测试\n已停止接收提交',
+ backgroundWatermark: '已弃用',
containerWidth: 400,
});
setupChartStructure(svgEl, resized);
- expect(svgEl.querySelectorAll('.historical-watermark')).toHaveLength(1);
- expect(svgEl.querySelector('.historical-watermark')?.getAttribute('width')).toBe(
- String(400 - resized.margin.left - resized.margin.right),
- );
- expect(svgEl.querySelector('.historical-watermark')?.textContent).toBe(
- '历史基准测试已停止接收提交',
+ expect(svgEl.querySelectorAll('.deprecated-watermark')).toHaveLength(1);
+ expect(svgEl.querySelector('.deprecated-watermark')?.getAttribute('width')).toBe(
+ String((400 - resized.margin.left - resized.margin.right) * 0.8),
);
+ expect(svgEl.querySelector('.deprecated-watermark')?.textContent).toBe('已弃用');
setupChartStructure(svgEl, defaultConfig({ watermark: 'logo' }));
- expect(svgEl.querySelector('.historical-watermark')).toBeNull();
+ expect(svgEl.querySelector('.deprecated-watermark')).toBeNull();
expect(svgEl.querySelector('.unofficial-watermark-image')).toBeNull();
expect(svgEl.querySelector('.watermark-rect')).not.toBeNull();
});
diff --git a/packages/app/src/lib/d3-chart/typography.ts b/packages/app/src/lib/d3-chart/typography.ts
index 7d22bebd0..f60a13613 100644
--- a/packages/app/src/lib/d3-chart/typography.ts
+++ b/packages/app/src/lib/d3-chart/typography.ts
@@ -7,8 +7,7 @@ export const CHART_TYPE = {
lineLabel: 13,
micro: 9,
watermark: 20,
- backgroundWatermarkTitle: 72,
- backgroundWatermarkSubtitle: 40,
+ backgroundWatermark: 108,
} as const;
export function px(size: number): string {
diff --git a/packages/app/src/lib/d3-chart/watermark.ts b/packages/app/src/lib/d3-chart/watermark.ts
index df68fd8f3..e20d4b0d9 100644
--- a/packages/app/src/lib/d3-chart/watermark.ts
+++ b/packages/app/src/lib/d3-chart/watermark.ts
@@ -12,39 +12,36 @@ export function updateBackgroundWatermark(
innerHeight: number,
margin: ChartMargin,
): void {
- svg.select('.historical-watermark').remove();
+ svg.select('.deprecated-watermark').remove();
if (!label) return;
+ // Main's centered logo occupies at most the middle 60% of the plot height.
+ // Keep the status below its bounding box without moving or resizing the logo.
const mark = svg
.insert('svg', '.chart-root')
- .attr('class', 'historical-watermark')
- .attr('data-testid', 'historical-watermark')
- .attr('x', margin.left)
- .attr('y', margin.top)
- .attr('width', innerWidth)
- .attr('height', innerHeight)
- .attr('viewBox', '0 0 1000 500')
+ .attr('class', 'deprecated-watermark')
+ .attr('data-testid', 'deprecated-watermark')
+ .attr('x', margin.left + innerWidth * 0.1)
+ .attr('y', margin.top + innerHeight * 0.82)
+ .attr('width', innerWidth * 0.8)
+ .attr('height', innerHeight * 0.16)
+ .attr('viewBox', '0 0 1000 200')
.attr('preserveAspectRatio', 'xMidYMid meet')
.attr('fill', 'currentColor')
.attr('opacity', 0.16)
.attr('pointer-events', 'none')
.attr('role', 'img')
- .attr('aria-label', label.replaceAll('\n', ' — '));
+ .attr('aria-label', label);
mark
- .selectAll('text')
- .data(label.split('\n'))
- .join('text')
+ .append('text')
.attr('x', 500)
- .attr('y', (_, index) => 230 + index * 80)
+ .attr('y', 100)
+ .attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle')
- .attr('font-size', (_, index) =>
- px(
- index === 0 ? CHART_TYPE.backgroundWatermarkTitle : CHART_TYPE.backgroundWatermarkSubtitle,
- ),
- )
+ .attr('font-size', px(CHART_TYPE.backgroundWatermark))
.attr('font-weight', 800)
- .text((line) => line);
+ .text(label);
}
function insertWatermarkRect(
diff --git a/packages/app/src/lib/views-api/docs/options.ts b/packages/app/src/lib/views-api/docs/options.ts
index f58ee4c17..ae4fb66e3 100644
--- a/packages/app/src/lib/views-api/docs/options.ts
+++ b/packages/app/src/lib/views-api/docs/options.ts
@@ -239,8 +239,8 @@ export const operations: ApiOperation[] = [
path: '/api/v1/views/options',
summary: text('Discover every views-API option domain', '获取 views API 的全部选项域'),
description: text(
- 'Static discovery endpoint for the views API: models (with DB keys, category and release date), sequences, precisions, hardware (with vendor, architecture, TDP and per-provider hourly cost), frameworks, speculative-decoding methods, percentiles, x-axis and scale modes, chart metrics (bilingual labels, unit, polarity), quick-filter domains, reliability ranges, and overview/calculator/fleet option sets — plus the defaults the dashboard itself uses. Values come from the same registries the dashboard renders its controls from, so this response is the authoritative input catalog for the other /api/v1/views endpoints. MiniMax M3 has category deprecated; its historical benchmark background watermark is presentation only and does not change view data. No database access.',
- 'views API 的静态发现端点:模型(含数据库键、分类与发布日期)、序列、精度、硬件(含厂商、架构、TDP 及各供应商每小时成本)、推理框架、投机解码方法、百分位、x 轴与坐标缩放模式、图表指标(中英文标签、单位、极性)、快捷筛选域、可靠性时间范围,以及 overview/calculator/fleet 的选项集,并附仪表盘实际使用的默认值。所有取值直接来自仪表盘控件渲染所用的注册表,因此该响应是其余 /api/v1/views 端点的权威输入目录。MiniMax M3 的分类为 deprecated;图表背景中的“历史基准测试 / 已停止接收提交”水印仅用于展示,不改变视图数据。不访问数据库。',
+ 'Static discovery endpoint for the views API: models (with DB keys, category and release date), sequences, precisions, hardware (with vendor, architecture, TDP and per-provider hourly cost), frameworks, speculative-decoding methods, percentiles, x-axis and scale modes, chart metrics (bilingual labels, unit, polarity), quick-filter domains, reliability ranges, and overview/calculator/fleet option sets — plus the defaults the dashboard itself uses. Values come from the same registries the dashboard renders its controls from, so this response is the authoritative input catalog for the other /api/v1/views endpoints. Models with category deprecated display a Deprecated background watermark below the existing SemiAnalysis logo. This is presentation only and does not change view data. No database access.',
+ 'views API 的静态发现端点:模型(含数据库键、分类与发布日期)、序列、精度、硬件(含厂商、架构、TDP 及各供应商每小时成本)、推理框架、投机解码方法、百分位、x 轴与坐标缩放模式、图表指标(中英文标签、单位、极性)、快捷筛选域、可靠性时间范围,以及 overview/calculator/fleet 的选项集,并附仪表盘实际使用的默认值。所有取值直接来自仪表盘控件渲染所用的注册表,因此该响应是其余 /api/v1/views 端点的权威输入目录。分类为 deprecated 的模型会在现有 SemiAnalysis 标志下方显示“已弃用”背景水印。该水印仅用于展示,不改变视图数据。不访问数据库。',
),
audience: 'public',
stability: 'beta',
diff --git a/packages/skills/skills/inferencex-api/integrity.json b/packages/skills/skills/inferencex-api/integrity.json
index 256fea3b2..099a3d588 100644
--- a/packages/skills/skills/inferencex-api/integrity.json
+++ b/packages/skills/skills/inferencex-api/integrity.json
@@ -9,7 +9,7 @@
"references/cli-contract.md": "4f070877eeefffad2860c58f79f68432fbe27aa21fc578a1c696f0309a3d7d7b",
"references/cli.md": "11e1183f7ed70d669019e8435ac16a6a9f873de027507b8850a9943f6659bfd9",
"references/collectivex.md": "f79cf8e2779a9289ab69f79f4a4b338e0b9a5f76a3750a3e969f33a744e726a1",
- "references/dashboard-views.md": "baa524758d8ef683c0c0a0acbc6229d3696be871ac733bf5a553defc0f7a9f98",
+ "references/dashboard-views.md": "d1d6a12d67bb78ac09a67fa714fd7bbd277867b6df8385cdf132ccd70718ab6d",
"references/offline-exports.md": "95aa565dcd4c9e592159baa560a9a58217bc61e395de1c6daf0576795c7f4ec9",
"references/pareto.md": "1b4d2d163f982e3f2d97addce310789eae9c1e5badd82dd7501708c9e0385027",
"references/powerx.md": "5af5527d54a7e6191a5769080c122638b333b1ceb59e19fdee7df662a5cd3c8f",
diff --git a/packages/skills/skills/inferencex-api/references/dashboard-views.md b/packages/skills/skills/inferencex-api/references/dashboard-views.md
index 4b3d201dd..72da65000 100644
--- a/packages/skills/skills/inferencex-api/references/dashboard-views.md
+++ b/packages/skills/skills/inferencex-api/references/dashboard-views.md
@@ -33,17 +33,19 @@ results or treat its interpolated values as separately measured operating points
for static model, hardware, sequence and metric registries; use each specialized
view's `options`, `configurations`, `workloads`, or raw run-list operation for
data-dependent choices.
- MiniMax M3 has `category=deprecated` in `options.models`; it remains selectable
- through explicit model parameters and overview `modelScope=all`. MiniMax M3.1
- Flash remains in the default model group. MiniMax M3 inference charts label
- historical results with a large “HISTORICAL BENCHMARK / SUBMISSIONS CLOSED”
- watermark behind the plot, including PNG exports and unofficial-run overlays.
- This watermark is presentation only; view API values and filters are unchanged.
-
- 中文:MiniMax M3 在 `options.models` 中的分类为 `deprecated`,仍可通过显式模型参数或
- overview 的 `modelScope=all` 查询;MiniMax M3.1 Flash 仍保留在默认模型组中。MiniMax M3
- 的推理图表会在绘图区背景中显示大号“历史基准测试 / 已停止接收提交”水印,PNG 导出和非官方
- run overlay 中同样保留。该水印仅用于展示,不改变视图 API 的数值或筛选行为。
+ Every model with `category=deprecated` in `options.models` receives a large
+ “Deprecated” plot watermark (“已弃用” in Chinese), including PNG exports and
+ unofficial-run overlays. It sits below the existing SemiAnalysis logo so the
+ marks do not overlap; logo styling and official-domain behavior follow the
+ main site. This is presentation only; view API values and filters are unchanged.
+ Deprecated models remain selectable through explicit model parameters and
+ overview `modelScope=all`. MiniMax M3 is deprecated; M3.1 Flash remains default.
+
+ 中文:`options.models` 中分类为 `deprecated` 的所有模型,都会在绘图区背景中显示大号
+ “已弃用”水印,PNG 导出和非官方 run overlay 中同样保留。水印放在现有 SemiAnalysis
+ 标志下方,两者不会重叠;标志样式及其在正式域名上的显示规则与主站一致。该水印仅用于
+ 展示,不改变视图 API 的数值或筛选行为。已弃用模型仍可通过显式模型参数或 overview 的
+ `modelScope=all` 查询。MiniMax M3 已弃用,M3.1 Flash 仍保留在默认模型组中。
2. Build one public HTTPS GET URL using `URLSearchParams`, especially for JSON
costs, commas, slashes, and run-specific comparison dates. No credentials are
From 4ff4ab7be0406011d17999a11d3f2e037c90cb77 Mon Sep 17 00:00:00 2001
From: Oseltamivir <58582368+Oseltamivir@users.noreply.github.com>
Date: Mon, 26 Oct 2026 15:47:43 +0800
Subject: [PATCH 5/5] fix: repeat Deprecated above and below the attribution
logo
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Render two non-overlapping status bands with stronger 30% opacity. Preserve the SemiAnalysis logo and verify both labels in PNG exports, overlays, and mobile layouts.
中文:在 SemiAnalysis 标志上方和下方分别显示“已弃用”水印,采用更清晰的 30% 不透明度,避免遮挡标志。验证 PNG 导出、非官方 overlay 和移动端均保留两处标注。
---
.../app/cypress/e2e/inference-chart.cy.ts | 18 ++++--
.../app/src/lib/d3-chart/chart-setup.test.ts | 14 ++++-
packages/app/src/lib/d3-chart/watermark.ts | 59 +++++++++++--------
.../app/src/lib/views-api/docs/options.ts | 4 +-
.../skills/inferencex-api/integrity.json | 2 +-
.../references/dashboard-views.md | 12 ++--
6 files changed, 65 insertions(+), 44 deletions(-)
diff --git a/packages/app/cypress/e2e/inference-chart.cy.ts b/packages/app/cypress/e2e/inference-chart.cy.ts
index 45d7df45c..5fa283b07 100644
--- a/packages/app/cypress/e2e/inference-chart.cy.ts
+++ b/packages/app/cypress/e2e/inference-chart.cy.ts
@@ -412,7 +412,11 @@ describe('Deprecated model background watermark', () => {
});
cy.get('#chart-0 svg[data-testid="d3-chart-svg"]').should('be.visible');
if (deprecated) {
- cy.get('#chart-0 .deprecated-watermark').should('have.text', 'Deprecated');
+ cy.get('#chart-0 .deprecated-watermark')
+ .should('have.length', 2)
+ .each(($mark) => {
+ cy.wrap($mark).should('have.text', 'Deprecated').and('have.attr', 'opacity', '0.3');
+ });
} else {
cy.get('#chart-0 .deprecated-watermark').should('not.exist');
}
@@ -455,13 +459,14 @@ describe('Deprecated model background watermark', () => {
.and('not.contain.text', 'Historical benchmark — submissions closed');
cy.get('#chart-0 svg [data-testid="deprecated-watermark"]').should('be.visible');
let exportedPng = '';
- let exportedCaption = '';
+ let exportedLabels: string[] = [];
cy.window().then((win) => {
win.HTMLAnchorElement.prototype.click = function () {
if (this.download.endsWith('.png')) {
exportedPng = this.href;
- exportedCaption =
- win.document.querySelector('#chart-0-export .deprecated-watermark')?.textContent ?? '';
+ exportedLabels = [
+ ...win.document.querySelectorAll('#chart-0-export .deprecated-watermark'),
+ ].map((mark) => mark.textContent ?? '');
}
};
});
@@ -470,7 +475,7 @@ describe('Deprecated model background watermark', () => {
cy.window()
.should(() => expect(exportedPng).to.match(/^data:image\/png;base64,/u))
.then(() => {
- expect(exportedCaption).to.contain('Deprecated');
+ expect(exportedLabels).to.deep.equal(['Deprecated', 'Deprecated']);
cy.writeFile(
'cypress/downloads/deprecated-model-watermark.png',
exportedPng.split(',')[1],
@@ -496,7 +501,8 @@ describe('Deprecated model background watermark', () => {
cy.wait('@historicalM3Overlay');
cy.get('#chart-0 svg [data-testid="deprecated-watermark"]')
.should('be.visible')
- .and('have.text', '已弃用');
+ .and('have.length', 2)
+ .each(($mark) => cy.wrap($mark).should('have.text', '已弃用'));
cy.document().then((doc) => {
expect(doc.documentElement.scrollWidth).to.be.at.most(doc.documentElement.clientWidth);
});
diff --git a/packages/app/src/lib/d3-chart/chart-setup.test.ts b/packages/app/src/lib/d3-chart/chart-setup.test.ts
index 1027c51d7..f7a673e3f 100644
--- a/packages/app/src/lib/d3-chart/chart-setup.test.ts
+++ b/packages/app/src/lib/d3-chart/chart-setup.test.ts
@@ -191,7 +191,7 @@ describe('setupChartStructure', () => {
[1200, 600],
[800, 300],
])(
- 'preserves the SemiAnalysis logo and places Deprecated below it at %s×%s',
+ 'preserves the SemiAnalysis logo and places Deprecated above and below it at %s×%s',
(containerWidth, containerHeight) => {
const svgEl = makeSvgEl();
const config = defaultConfig({ watermark: 'logo', containerWidth, containerHeight });
@@ -203,7 +203,15 @@ describe('setupChartStructure', () => {
expect(svgEl.querySelector('#logo-pattern-test-chart')!.outerHTML).toBe(originalLogo);
expect(svgEl.querySelector('.watermark-rect')!.outerHTML).toBe(originalRect);
const logo = svgEl.querySelector('#logo-pattern-test-chart image')!;
- const status = svgEl.querySelector('.deprecated-watermark')!;
+ const above = svgEl.querySelector('.deprecated-watermark[data-position="above"]')!;
+ const status = svgEl.querySelector('.deprecated-watermark[data-position="below"]')!;
+ expect(svgEl.querySelectorAll('.deprecated-watermark')).toHaveLength(2);
+ expect(Number(above.getAttribute('y'))).toBeGreaterThanOrEqual(config.margin.top);
+ expect(Number(above.getAttribute('y')) + Number(above.getAttribute('height'))).toBeLessThan(
+ Number(logo.getAttribute('y')),
+ );
+ expect(above.getAttribute('opacity')).toBe('0.3');
+ expect(status.getAttribute('opacity')).toBe('0.3');
const logoBottom = Number(logo.getAttribute('y')) + Number(logo.getAttribute('height'));
expect(Number(status.getAttribute('y'))).toBeGreaterThan(logoBottom);
expect(
@@ -238,7 +246,7 @@ describe('setupChartStructure', () => {
containerWidth: 400,
});
setupChartStructure(svgEl, resized);
- expect(svgEl.querySelectorAll('.deprecated-watermark')).toHaveLength(1);
+ expect(svgEl.querySelectorAll('.deprecated-watermark')).toHaveLength(2);
expect(svgEl.querySelector('.deprecated-watermark')?.getAttribute('width')).toBe(
String((400 - resized.margin.left - resized.margin.right) * 0.8),
);
diff --git a/packages/app/src/lib/d3-chart/watermark.ts b/packages/app/src/lib/d3-chart/watermark.ts
index e20d4b0d9..56805b888 100644
--- a/packages/app/src/lib/d3-chart/watermark.ts
+++ b/packages/app/src/lib/d3-chart/watermark.ts
@@ -12,36 +12,43 @@ export function updateBackgroundWatermark(
innerHeight: number,
margin: ChartMargin,
): void {
- svg.select('.deprecated-watermark').remove();
+ svg.selectAll('.deprecated-watermark').remove();
if (!label) return;
// Main's centered logo occupies at most the middle 60% of the plot height.
- // Keep the status below its bounding box without moving or resizing the logo.
- const mark = svg
- .insert('svg', '.chart-root')
- .attr('class', 'deprecated-watermark')
- .attr('data-testid', 'deprecated-watermark')
- .attr('x', margin.left + innerWidth * 0.1)
- .attr('y', margin.top + innerHeight * 0.82)
- .attr('width', innerWidth * 0.8)
- .attr('height', innerHeight * 0.16)
- .attr('viewBox', '0 0 1000 200')
- .attr('preserveAspectRatio', 'xMidYMid meet')
- .attr('fill', 'currentColor')
- .attr('opacity', 0.16)
- .attr('pointer-events', 'none')
- .attr('role', 'img')
- .attr('aria-label', label);
+ // Reserve separate bands above and below it without moving or resizing it.
+ for (const [position, offset] of [
+ ['above', 0.02],
+ ['below', 0.82],
+ ] as const) {
+ const mark = svg
+ .insert('svg', '.chart-root')
+ .attr('class', 'deprecated-watermark')
+ .attr('data-testid', 'deprecated-watermark')
+ .attr('data-position', position)
+ .attr('x', margin.left + innerWidth * 0.1)
+ .attr('y', margin.top + innerHeight * offset)
+ .attr('width', innerWidth * 0.8)
+ .attr('height', innerHeight * 0.16)
+ .attr('viewBox', '0 0 1000 200')
+ .attr('preserveAspectRatio', 'xMidYMid meet')
+ .attr('fill', 'currentColor')
+ .attr('opacity', 0.3)
+ .attr('pointer-events', 'none')
+ .attr('role', 'img')
+ .attr('aria-label', label)
+ .attr('aria-hidden', position === 'below' ? 'true' : null);
- mark
- .append('text')
- .attr('x', 500)
- .attr('y', 100)
- .attr('dominant-baseline', 'middle')
- .attr('text-anchor', 'middle')
- .attr('font-size', px(CHART_TYPE.backgroundWatermark))
- .attr('font-weight', 800)
- .text(label);
+ mark
+ .append('text')
+ .attr('x', 500)
+ .attr('y', 100)
+ .attr('dominant-baseline', 'middle')
+ .attr('text-anchor', 'middle')
+ .attr('font-size', px(CHART_TYPE.backgroundWatermark))
+ .attr('font-weight', 800)
+ .text(label);
+ }
}
function insertWatermarkRect(
diff --git a/packages/app/src/lib/views-api/docs/options.ts b/packages/app/src/lib/views-api/docs/options.ts
index ae4fb66e3..3c7e3b2d0 100644
--- a/packages/app/src/lib/views-api/docs/options.ts
+++ b/packages/app/src/lib/views-api/docs/options.ts
@@ -239,8 +239,8 @@ export const operations: ApiOperation[] = [
path: '/api/v1/views/options',
summary: text('Discover every views-API option domain', '获取 views API 的全部选项域'),
description: text(
- 'Static discovery endpoint for the views API: models (with DB keys, category and release date), sequences, precisions, hardware (with vendor, architecture, TDP and per-provider hourly cost), frameworks, speculative-decoding methods, percentiles, x-axis and scale modes, chart metrics (bilingual labels, unit, polarity), quick-filter domains, reliability ranges, and overview/calculator/fleet option sets — plus the defaults the dashboard itself uses. Values come from the same registries the dashboard renders its controls from, so this response is the authoritative input catalog for the other /api/v1/views endpoints. Models with category deprecated display a Deprecated background watermark below the existing SemiAnalysis logo. This is presentation only and does not change view data. No database access.',
- 'views API 的静态发现端点:模型(含数据库键、分类与发布日期)、序列、精度、硬件(含厂商、架构、TDP 及各供应商每小时成本)、推理框架、投机解码方法、百分位、x 轴与坐标缩放模式、图表指标(中英文标签、单位、极性)、快捷筛选域、可靠性时间范围,以及 overview/calculator/fleet 的选项集,并附仪表盘实际使用的默认值。所有取值直接来自仪表盘控件渲染所用的注册表,因此该响应是其余 /api/v1/views 端点的权威输入目录。分类为 deprecated 的模型会在现有 SemiAnalysis 标志下方显示“已弃用”背景水印。该水印仅用于展示,不改变视图数据。不访问数据库。',
+ 'Static discovery endpoint for the views API: models (with DB keys, category and release date), sequences, precisions, hardware (with vendor, architecture, TDP and per-provider hourly cost), frameworks, speculative-decoding methods, percentiles, x-axis and scale modes, chart metrics (bilingual labels, unit, polarity), quick-filter domains, reliability ranges, and overview/calculator/fleet option sets — plus the defaults the dashboard itself uses. Values come from the same registries the dashboard renders its controls from, so this response is the authoritative input catalog for the other /api/v1/views endpoints. Models with category deprecated display Deprecated background watermarks in separate bands above and below the existing SemiAnalysis logo. This is presentation only and does not change view data. No database access.',
+ 'views API 的静态发现端点:模型(含数据库键、分类与发布日期)、序列、精度、硬件(含厂商、架构、TDP 及各供应商每小时成本)、推理框架、投机解码方法、百分位、x 轴与坐标缩放模式、图表指标(中英文标签、单位、极性)、快捷筛选域、可靠性时间范围,以及 overview/calculator/fleet 的选项集,并附仪表盘实际使用的默认值。所有取值直接来自仪表盘控件渲染所用的注册表,因此该响应是其余 /api/v1/views 端点的权威输入目录。分类为 deprecated 的模型会在现有 SemiAnalysis 标志上方和下方的独立区域显示“已弃用”背景水印。该水印仅用于展示,不改变视图数据。不访问数据库。',
),
audience: 'public',
stability: 'beta',
diff --git a/packages/skills/skills/inferencex-api/integrity.json b/packages/skills/skills/inferencex-api/integrity.json
index 099a3d588..7dd040c1c 100644
--- a/packages/skills/skills/inferencex-api/integrity.json
+++ b/packages/skills/skills/inferencex-api/integrity.json
@@ -9,7 +9,7 @@
"references/cli-contract.md": "4f070877eeefffad2860c58f79f68432fbe27aa21fc578a1c696f0309a3d7d7b",
"references/cli.md": "11e1183f7ed70d669019e8435ac16a6a9f873de027507b8850a9943f6659bfd9",
"references/collectivex.md": "f79cf8e2779a9289ab69f79f4a4b338e0b9a5f76a3750a3e969f33a744e726a1",
- "references/dashboard-views.md": "d1d6a12d67bb78ac09a67fa714fd7bbd277867b6df8385cdf132ccd70718ab6d",
+ "references/dashboard-views.md": "333e4473a6027a45992a01bc2e908437dc6b5c43542dd481b0929ba95027f506",
"references/offline-exports.md": "95aa565dcd4c9e592159baa560a9a58217bc61e395de1c6daf0576795c7f4ec9",
"references/pareto.md": "1b4d2d163f982e3f2d97addce310789eae9c1e5badd82dd7501708c9e0385027",
"references/powerx.md": "5af5527d54a7e6191a5769080c122638b333b1ceb59e19fdee7df662a5cd3c8f",
diff --git a/packages/skills/skills/inferencex-api/references/dashboard-views.md b/packages/skills/skills/inferencex-api/references/dashboard-views.md
index 72da65000..f2ad25963 100644
--- a/packages/skills/skills/inferencex-api/references/dashboard-views.md
+++ b/packages/skills/skills/inferencex-api/references/dashboard-views.md
@@ -33,17 +33,17 @@ results or treat its interpolated values as separately measured operating points
for static model, hardware, sequence and metric registries; use each specialized
view's `options`, `configurations`, `workloads`, or raw run-list operation for
data-dependent choices.
- Every model with `category=deprecated` in `options.models` receives a large
- “Deprecated” plot watermark (“已弃用” in Chinese), including PNG exports and
- unofficial-run overlays. It sits below the existing SemiAnalysis logo so the
- marks do not overlap; logo styling and official-domain behavior follow the
+ Every model with `category=deprecated` in `options.models` receives large
+ “Deprecated” plot watermarks (“已弃用” in Chinese), including PNG exports and
+ unofficial-run overlays. They appear in separate bands above and below the existing SemiAnalysis logo
+ so the marks do not overlap; logo styling and official-domain behavior follow the
main site. This is presentation only; view API values and filters are unchanged.
Deprecated models remain selectable through explicit model parameters and
overview `modelScope=all`. MiniMax M3 is deprecated; M3.1 Flash remains default.
中文:`options.models` 中分类为 `deprecated` 的所有模型,都会在绘图区背景中显示大号
- “已弃用”水印,PNG 导出和非官方 run overlay 中同样保留。水印放在现有 SemiAnalysis
- 标志下方,两者不会重叠;标志样式及其在正式域名上的显示规则与主站一致。该水印仅用于
+ “已弃用”水印,PNG 导出和非官方 run overlay 中同样保留。水印分别放在现有 SemiAnalysis
+ 标志的上方和下方,彼此不会重叠;标志样式及其在正式域名上的显示规则与主站一致。该水印仅用于
展示,不改变视图 API 的数值或筛选行为。已弃用模型仍可通过显式模型参数或 overview 的
`modelScope=all` 查询。MiniMax M3 已弃用,M3.1 Flash 仍保留在默认模型组中。