diff --git a/packages/app/cypress/e2e/throughput-calculator.cy.ts b/packages/app/cypress/e2e/throughput-calculator.cy.ts
index 3cdeaf68..362e94c0 100644
--- a/packages/app/cypress/e2e/throughput-calculator.cy.ts
+++ b/packages/app/cypress/e2e/throughput-calculator.cy.ts
@@ -521,5 +521,22 @@ describe('TCO Calculator', () => {
cy.get('[data-testid="calculator-controls"]').should('be.visible');
cy.get('[data-testid="calculator-bar-chart"] svg .bar').should('have.length.greaterThan', 0);
});
+
+ // Regression: SSR'd HTML must reflect the URL-supplied model so share links
+ // open straight to the right model without a flash of the default. See #430.
+ it('?g_model= seeds the model selector before client hydration', () => {
+ cy.request('/calculator?g_model=DeepSeek-V4-Pro').then((response) => {
+ expect(response.body).to.contain('DeepSeek V4 Pro 1.6T');
+ expect(response.body).not.to.contain('DeepSeek R1 0528 671B');
+ });
+ });
+
+ it('renders the URL-supplied model in the dropdown after navigating', () => {
+ cy.window().then((win) => {
+ win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
+ });
+ cy.visit('/calculator?g_model=DeepSeek-V4-Pro');
+ cy.get('[data-testid="calc-model-selector"]').should('contain.text', 'DeepSeek V4 Pro 1.6T');
+ });
});
});
diff --git a/packages/app/src/app/(dashboard)/calculator/page.tsx b/packages/app/src/app/(dashboard)/calculator/page.tsx
index 0bd2ead4..7b11ff34 100644
--- a/packages/app/src/app/(dashboard)/calculator/page.tsx
+++ b/packages/app/src/app/(dashboard)/calculator/page.tsx
@@ -1,10 +1,17 @@
import type { Metadata } from 'next';
import ThroughputCalculatorDisplay from '@/components/calculator/ThroughputCalculatorDisplay';
+import { resolveCalculatorUrlSeed } from '@/components/calculator/url-seed';
import { tabMetadata } from '@/lib/tab-meta';
export const metadata: Metadata = tabMetadata('calculator');
-export default function CalculatorPage() {
- return ;
+interface Props {
+ searchParams: Promise>;
+}
+
+export default async function CalculatorPage({ searchParams }: Props) {
+ const sp = await searchParams;
+ const seed = resolveCalculatorUrlSeed(sp);
+ return ;
}
diff --git a/packages/app/src/components/calculator/ThroughputCalculatorDisplay.tsx b/packages/app/src/components/calculator/ThroughputCalculatorDisplay.tsx
index 4586dab4..521e45b2 100644
--- a/packages/app/src/components/calculator/ThroughputCalculatorDisplay.tsx
+++ b/packages/app/src/components/calculator/ThroughputCalculatorDisplay.tsx
@@ -6,7 +6,8 @@ import { BarChart3, Table2 } from 'lucide-react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import CalculatorTable from '@/components/calculator/CalculatorTable';
-import { useGlobalFilters } from '@/components/GlobalFilterContext';
+import type { CalculatorUrlSeed } from '@/components/calculator/url-seed';
+import { GlobalFilterProvider, useGlobalFilters } from '@/components/GlobalFilterContext';
import { Badge } from '@/components/ui/badge';
import { Card } from '@/components/ui/card';
import { ChartButtons } from '@/components/ui/chart-buttons';
@@ -93,7 +94,22 @@ const CALCULATOR_VIEW_MODE_OPTIONS: SegmentedToggleOption[]
const CALCULATOR_MOBILE_VIEW_MODE_OPTIONS: SegmentedToggleOption[] =
CALCULATOR_VIEW_MODE_OPTIONS.map(({ testId: _testId, ...option }) => option);
-export default function ThroughputCalculatorDisplay() {
+export default function ThroughputCalculatorDisplay({ urlSeed }: { urlSeed?: CalculatorUrlSeed }) {
+ if (urlSeed && (urlSeed.model || urlSeed.sequence || urlSeed.precisions)) {
+ return (
+
+
+
+ );
+ }
+ return ;
+}
+
+function ThroughputCalculatorInner() {
const [openDropdown, setOpenDropdown] = useState(null);
const handleDropdownOpenChange = (dropdownKey: string) => (isOpen: boolean) => {
if (isOpen) {
diff --git a/packages/app/src/components/calculator/url-seed.test.ts b/packages/app/src/components/calculator/url-seed.test.ts
new file mode 100644
index 00000000..8d590b6a
--- /dev/null
+++ b/packages/app/src/components/calculator/url-seed.test.ts
@@ -0,0 +1,56 @@
+import { describe, expect, it } from 'vitest';
+
+import { resolveCalculatorUrlSeed } from './url-seed';
+import { Model, Precision, Sequence } from '@/lib/data-mappings';
+
+describe('resolveCalculatorUrlSeed', () => {
+ it('returns the model when g_model is a known enum value', () => {
+ expect(resolveCalculatorUrlSeed({ g_model: 'DeepSeek-V4-Pro' })).toEqual({
+ model: Model.DeepSeek_V4_Pro,
+ });
+ });
+
+ it('ignores unknown g_model values so SSR falls back to the default', () => {
+ expect(resolveCalculatorUrlSeed({ g_model: 'not-a-model' })).toEqual({});
+ });
+
+ it('returns the sequence when i_seq is a known enum value', () => {
+ expect(resolveCalculatorUrlSeed({ i_seq: '1k/1k' })).toEqual({
+ sequence: Sequence.OneK_OneK,
+ });
+ });
+
+ it('parses i_prec as a comma-separated list, dropping unknown precisions', () => {
+ expect(resolveCalculatorUrlSeed({ i_prec: 'fp8,not-real,bf16' })).toEqual({
+ precisions: [Precision.FP8, Precision.BF16],
+ });
+ });
+
+ it('omits precisions when none of the supplied values are known', () => {
+ expect(resolveCalculatorUrlSeed({ i_prec: 'garbage' })).toEqual({});
+ });
+
+ it('combines model, sequence, and precisions from the same URL', () => {
+ expect(
+ resolveCalculatorUrlSeed({
+ g_model: 'DeepSeek-V4-Pro',
+ i_seq: '1k/8k',
+ i_prec: 'fp4,fp8',
+ }),
+ ).toEqual({
+ model: Model.DeepSeek_V4_Pro,
+ sequence: Sequence.OneK_EightK,
+ precisions: [Precision.FP4, Precision.FP8],
+ });
+ });
+
+ it('picks the first value when a param is repeated as an array', () => {
+ expect(resolveCalculatorUrlSeed({ g_model: ['DeepSeek-V4-Pro', 'GLM-5'] })).toEqual({
+ model: Model.DeepSeek_V4_Pro,
+ });
+ });
+
+ it('returns an empty seed for an empty searchParams object', () => {
+ expect(resolveCalculatorUrlSeed({})).toEqual({});
+ });
+});
diff --git a/packages/app/src/components/calculator/url-seed.ts b/packages/app/src/components/calculator/url-seed.ts
new file mode 100644
index 00000000..c41f74b2
--- /dev/null
+++ b/packages/app/src/components/calculator/url-seed.ts
@@ -0,0 +1,56 @@
+import {
+ Model,
+ MODEL_OPTIONS,
+ Precision,
+ PRECISION_OPTIONS,
+ Sequence,
+ SEQUENCE_OPTIONS,
+} from '@/lib/data-mappings';
+
+export interface CalculatorUrlSeed {
+ model?: Model;
+ sequence?: Sequence;
+ precisions?: string[];
+}
+
+function pickString(value: string | string[] | undefined): string | undefined {
+ if (typeof value === 'string') return value;
+ if (Array.isArray(value)) return value[0];
+ return undefined;
+}
+
+/**
+ * Read the URL params that the calculator can SSR with into a typed seed.
+ * Without this, `?g_model=DeepSeek-V4-Pro` only takes effect after client
+ * hydration runs the `useLayoutEffect` in `GlobalFilterContext` — so the
+ * initial paint (and any preview/scraper that doesn't run JS) shows the
+ * default model instead of the shared one.
+ */
+export function resolveCalculatorUrlSeed(
+ sp: Record,
+): CalculatorUrlSeed {
+ const seed: CalculatorUrlSeed = {};
+
+ const modelParam = pickString(sp.g_model);
+ if (modelParam && (MODEL_OPTIONS as readonly string[]).includes(modelParam)) {
+ seed.model = modelParam as Model;
+ }
+
+ const seqParam = pickString(sp.i_seq);
+ if (seqParam && (SEQUENCE_OPTIONS as readonly string[]).includes(seqParam)) {
+ seed.sequence = seqParam as Sequence;
+ }
+
+ const precParam = pickString(sp.i_prec);
+ if (precParam) {
+ const precs = precParam
+ .split(',')
+ .filter((p) => (PRECISION_OPTIONS as readonly string[]).includes(p));
+ if (precs.length > 0) seed.precisions = precs;
+ }
+
+ return seed;
+}
+
+// Re-export Model/Precision/Sequence for callers that already import this module.
+export { Model, Precision, Sequence };