diff --git a/docs/state-ownership.md b/docs/state-ownership.md index 29d63dfd..0c3eb993 100644 --- a/docs/state-ownership.md +++ b/docs/state-ownership.md @@ -73,7 +73,7 @@ Depends on: `GlobalFilterProvider` (reads all filter state and availability, inc - `selectedYAxisMetric` (`i_metric`), `selectedXAxisMetric` (`i_xmetric`), `selectedE2eXAxisMetric` (`i_e2e_xmetric`) - `scaleType` — `auto | linear | log` (`i_scale`) -- `hideNonOptimal` (`i_optimal`), `hidePointLabels` (`i_nolabel`), `logScale` (`i_log`) +- `hideNonOptimal` (`i_optimal`), `showPointLabels` (`i_label`), `logScale` (`i_log`) - `highContrast` (`i_hc`), `isLegendExpanded` (`i_legend`) - `useAdvancedLabels` (`i_advlabel`), `showGradientLabels` (`i_gradlabel`) - `colorShuffleSeed` — no URL param; ephemeral @@ -260,34 +260,35 @@ Historical Trends and TCO Calculator share the inference tab's URL path (`/infer ### Full parameter list -| Param | Owner | Default | -| --------------- | ------------------- | -------------------------------- | -| `g_model` | GlobalFilterContext | `DeepSeek-R1-0528` | -| `g_rundate` | GlobalFilterContext | `''` | -| `g_runid` | GlobalFilterContext | `''` | -| `i_seq` | GlobalFilterContext | `8k/1k` | -| `i_prec` | GlobalFilterContext | `fp4` | -| `i_metric` | InferenceProvider | `y_tpPerGpu` | -| `i_xmetric` | InferenceProvider | `p99_ttft` | -| `i_e2e_xmetric` | InferenceProvider | `''` | -| `i_scale` | InferenceProvider | `auto` | -| `i_gpus` | InferenceProvider | `''` | -| `i_dates` | InferenceProvider | `''` | -| `i_dstart` | InferenceProvider | `''` | -| `i_dend` | InferenceProvider | `''` | -| `i_optimal` | InferenceProvider | `''` (truthy = hide non-optimal) | -| `i_nolabel` | InferenceProvider | `''` | -| `i_hc` | InferenceProvider | `''` | -| `i_log` | InferenceProvider | `''` | -| `i_legend` | InferenceProvider | `''` | -| `i_advlabel` | InferenceProvider | `''` | -| `i_gradlabel` | InferenceProvider | `''` | -| `e_rundate` | EvaluationProvider | `''` | -| `e_bench` | EvaluationProvider | `''` | -| `e_hc` | EvaluationProvider | `''` | -| `e_labels` | EvaluationProvider | `''` | -| `e_legend` | EvaluationProvider | `''` | -| `r_range` | ReliabilityProvider | `last-3-months` | -| `r_pct` | ReliabilityProvider | `''` | -| `r_hc` | ReliabilityProvider | `''` | -| `r_legend` | ReliabilityProvider | `''` | +| Param | Owner | Default | +| --------------- | ------------------- | --------------------------------- | +| `g_model` | GlobalFilterContext | `DeepSeek-R1-0528` | +| `g_rundate` | GlobalFilterContext | `''` | +| `g_runid` | GlobalFilterContext | `''` | +| `i_seq` | GlobalFilterContext | `8k/1k` | +| `i_prec` | GlobalFilterContext | `fp4` | +| `i_metric` | InferenceProvider | `y_tpPerGpu` | +| `i_xmetric` | InferenceProvider | `p99_ttft` | +| `i_e2e_xmetric` | InferenceProvider | `''` | +| `i_scale` | InferenceProvider | `auto` | +| `i_gpus` | InferenceProvider | `''` | +| `i_dates` | InferenceProvider | `''` | +| `i_dstart` | InferenceProvider | `''` | +| `i_dend` | InferenceProvider | `''` | +| `i_optimal` | InferenceProvider | `''` (truthy = hide non-optimal) | +| `i_label` | InferenceProvider | `''` (truthy = show point labels) | +| `i_nolabel` | InferenceProvider | `''` (legacy, read-only) | +| `i_hc` | InferenceProvider | `''` | +| `i_log` | InferenceProvider | `''` | +| `i_legend` | InferenceProvider | `''` | +| `i_advlabel` | InferenceProvider | `''` | +| `i_gradlabel` | InferenceProvider | `''` | +| `e_rundate` | EvaluationProvider | `''` | +| `e_bench` | EvaluationProvider | `''` | +| `e_hc` | EvaluationProvider | `''` | +| `e_labels` | EvaluationProvider | `''` | +| `e_legend` | EvaluationProvider | `''` | +| `r_range` | ReliabilityProvider | `last-3-months` | +| `r_pct` | ReliabilityProvider | `''` | +| `r_hc` | ReliabilityProvider | `''` | +| `r_legend` | ReliabilityProvider | `''` | diff --git a/packages/app/cypress/e2e/line-labels.cy.ts b/packages/app/cypress/e2e/line-labels.cy.ts index b828b872..84e655f8 100644 --- a/packages/app/cypress/e2e/line-labels.cy.ts +++ b/packages/app/cypress/e2e/line-labels.cy.ts @@ -15,15 +15,22 @@ describe('Line Labels Toggle', () => { cy.get('label[for="scatter-line-labels"]').should('contain.text', 'Line Labels'); }); - it('Line Labels toggle is off by default', () => { - cy.get('#scatter-line-labels').should('have.attr', 'data-state', 'unchecked'); + it('Line Labels toggle is on by default', () => { + cy.get('#scatter-line-labels').should('have.attr', 'data-state', 'checked'); + + // Line labels render without any interaction + cy.get('[data-testid="scatter-graph"] svg g.line-label').should('have.length.greaterThan', 0); }); - it('toggling Line Labels on renders label elements on the chart', () => { + it('toggling Line Labels off then back on removes and restores label elements', () => { + // On by default — turn it off first. cy.get('#scatter-line-labels').click(); - cy.get('#scatter-line-labels').should('have.attr', 'data-state', 'checked'); + cy.get('#scatter-line-labels').should('have.attr', 'data-state', 'unchecked'); + cy.get('[data-testid="scatter-graph"] svg g.line-label').should('have.length', 0); - // Line label groups should appear in the SVG + // Turn it back on — labels return. + cy.get('#scatter-line-labels').click(); + cy.get('#scatter-line-labels').should('have.attr', 'data-state', 'checked'); cy.get('[data-testid="scatter-graph"] svg g.line-label').should('have.length.greaterThan', 0); }); @@ -148,6 +155,53 @@ describe('Line Labels Toggle', () => { cy.get('[data-testid="scatter-graph"] svg g.line-label').should('have.length.greaterThan', 0); }); + it('URL param i_linelabel=0 disables line labels on load', () => { + cy.visit('/inference?i_linelabel=0', { + onBeforeLoad(win) { + win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now())); + }, + }); + cy.get('[data-testid="scatter-graph"]').should('be.visible'); + cy.get('#scatter-line-labels').should('have.attr', 'data-state', 'unchecked'); + + // Labels should not be rendered + cy.get('[data-testid="scatter-graph"] svg g.line-label').should('have.length', 0); + }); + + it('legacy URL param i_nolabel=1 keeps point labels hidden on load', () => { + cy.visit('/inference?i_nolabel=1', { + onBeforeLoad(win) { + win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now())); + }, + }); + cy.get('[data-testid="scatter-graph"]').should('be.visible'); + cy.get('#scatter-point-labels').should('have.attr', 'data-state', 'unchecked'); + }); + + it('legacy URL param i_advlabel=1 auto-enables Labels so advanced labels render', () => { + cy.visit('/inference?i_advlabel=1', { + onBeforeLoad(win) { + win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now())); + }, + }); + cy.get('[data-testid="scatter-graph"]').should('be.visible'); + cy.get('#scatter-parallelism-labels').should('have.attr', 'data-state', 'checked'); + // Labels toggle is auto-enabled by the URL hydration so the advanced + // (parallelism) point labels actually render. + cy.get('#scatter-point-labels').should('have.attr', 'data-state', 'checked'); + }); + + it('legacy URL combo i_advlabel=1&i_nolabel=1 keeps point labels hidden (i_nolabel wins)', () => { + cy.visit('/inference?i_advlabel=1&i_nolabel=1', { + onBeforeLoad(win) { + win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now())); + }, + }); + cy.get('[data-testid="scatter-graph"]').should('be.visible'); + cy.get('#scatter-parallelism-labels').should('have.attr', 'data-state', 'checked'); + cy.get('#scatter-point-labels').should('have.attr', 'data-state', 'unchecked'); + }); + it('appends the precision to each line label when multiple precisions are selected', () => { // Pair the FP4+FP8 selection with a model that has both precisions in the // fixtures. The default model (DeepSeek-V4-Pro) only has FP4, so diff --git a/packages/app/cypress/support/mock-data.ts b/packages/app/cypress/support/mock-data.ts index 7e300f45..b7380eb1 100644 --- a/packages/app/cypress/support/mock-data.ts +++ b/packages/app/cypress/support/mock-data.ts @@ -199,8 +199,8 @@ export function createMockInferenceContext( setIsLegendExpanded: namedStub('setIsLegendExpanded'), hideNonOptimal: false, setHideNonOptimal: namedStub('setHideNonOptimal'), - hidePointLabels: false, - setHidePointLabels: namedStub('setHidePointLabels'), + showPointLabels: false, + setShowPointLabels: namedStub('setShowPointLabels'), highContrast: false, setHighContrast: namedStub('setHighContrast'), logScale: false, diff --git a/packages/app/src/components/inference/InferenceContext.tsx b/packages/app/src/components/inference/InferenceContext.tsx index 2b42650f..452a6307 100644 --- a/packages/app/src/components/inference/InferenceContext.tsx +++ b/packages/app/src/components/inference/InferenceContext.tsx @@ -157,7 +157,17 @@ export function InferenceProvider({ }); const [hideNonOptimal, setHideNonOptimal] = useState(() => getUrlParam('i_optimal') !== '0'); - const [hidePointLabels, setHidePointLabels] = useState(() => getUrlParam('i_nolabel') === '1'); + const [showPointLabels, setShowPointLabels] = useState(() => { + // Legacy `?i_nolabel=1` from before the rename: keep hiding point labels + // explicitly so the share link's intent survives future default changes. + if (getUrlParam('i_nolabel') === '1') return false; + if (getUrlParam('i_label') === '1') return true; + // Old share links set `?i_advlabel=1` while keeping the labels default + // (shown). Mirror the toggle's auto-enable side-effect on load so those + // links still render advanced labels under the new default-off behavior. + if (getUrlParam('i_advlabel') === '1') return true; + return false; + }); const [logScale, setLogScale] = useState(() => getUrlParam('i_log') === '1'); const [useAdvancedLabels, setUseAdvancedLabels] = useState( () => getUrlParam('i_advlabel') === '1', @@ -165,7 +175,7 @@ export function InferenceProvider({ const [showGradientLabels, setShowGradientLabels] = useState( () => getUrlParam('i_gradlabel') === '1', ); - const [showLineLabels, setShowLineLabels] = useState(() => getUrlParam('i_linelabel') === '1'); + const [showLineLabels, setShowLineLabels] = useState(() => getUrlParam('i_linelabel') !== '0'); const [showSpeedOverlay, setShowSpeedOverlay] = useState(() => getUrlParam('i_speed') === '1'); const [showMinecraftOverlay, setShowMinecraftOverlay] = useState( () => getUrlParam('i_mc') === '1', @@ -821,7 +831,7 @@ export function InferenceProvider({ i_dstart: selectedDateRange.startDate, i_dend: selectedDateRange.endDate, i_optimal: hideNonOptimal ? '' : '0', - i_nolabel: hidePointLabels ? '1' : '', + i_label: showPointLabels ? '1' : '', i_hc: highContrast ? '1' : '', i_log: logScale ? '1' : '', i_xmetric: selectedXAxisMetric || '', @@ -830,7 +840,7 @@ export function InferenceProvider({ i_legend: isLegendExpanded ? '' : '0', i_advlabel: useAdvancedLabels ? '1' : '', i_gradlabel: showGradientLabels ? '1' : '', - i_linelabel: showLineLabels ? '1' : '', + i_linelabel: showLineLabels ? '' : '0', i_speed: showSpeedOverlay ? '1' : '', i_mc: showMinecraftOverlay ? '1' : '', i_active: iActiveStr, @@ -844,7 +854,7 @@ export function InferenceProvider({ selectedDates, selectedDateRange, hideNonOptimal, - hidePointLabels, + showPointLabels, highContrast, logScale, isLegendExpanded, @@ -989,8 +999,8 @@ export function InferenceProvider({ setIsLegendExpanded, hideNonOptimal, setHideNonOptimal, - hidePointLabels, - setHidePointLabels, + showPointLabels, + setShowPointLabels, highContrast, setHighContrast, logScale, @@ -1089,7 +1099,7 @@ export function InferenceProvider({ availableSequences, availableModels, hideNonOptimal, - hidePointLabels, + showPointLabels, highContrast, logScale, isLegendExpanded, diff --git a/packages/app/src/components/inference/types.ts b/packages/app/src/components/inference/types.ts index 6c146457..ce11acf7 100644 --- a/packages/app/src/components/inference/types.ts +++ b/packages/app/src/components/inference/types.ts @@ -645,8 +645,8 @@ export interface InferenceChartContextType { isLegendExpanded: boolean; hideNonOptimal: boolean; setHideNonOptimal: (hide: boolean) => void; - hidePointLabels: boolean; - setHidePointLabels: (hide: boolean) => void; + showPointLabels: boolean; + setShowPointLabels: (show: boolean) => void; highContrast: boolean; setHighContrast: (highContrast: boolean) => void; logScale: boolean; diff --git a/packages/app/src/components/inference/ui/GPUGraph.tsx b/packages/app/src/components/inference/ui/GPUGraph.tsx index 3a3f2c86..df22b8f5 100644 --- a/packages/app/src/components/inference/ui/GPUGraph.tsx +++ b/packages/app/src/components/inference/ui/GPUGraph.tsx @@ -92,8 +92,8 @@ const GPUGraph = React.memo( activeDates, hideNonOptimal, setHideNonOptimal, - hidePointLabels, - setHidePointLabels, + showPointLabels, + setShowPointLabels, logScale, setLogScale, isLegendExpanded, @@ -754,7 +754,7 @@ const GPUGraph = React.memo( data: filteredData, config: { getColor, - hideLabels: hidePointLabels, + hideLabels: !showPointLabels, getLabelText: (d) => (useAdvancedLabels ? getPointLabel(d) : String(d.tp)), foreground: 'var(--foreground)', dataAttrs: { @@ -876,12 +876,12 @@ const GPUGraph = React.memo( }, }, { - id: 'gpu-hide-point-labels', - label: 'Hide Labels', - checked: hidePointLabels, + id: 'gpu-point-labels', + label: 'Labels', + checked: showPointLabels, onCheckedChange: (c) => { - setHidePointLabels(c); - track('interactivity_hide_point_labels_toggled', { enabled: c }); + setShowPointLabels(c); + track('interactivity_point_labels_toggled', { enabled: c }); }, }, { @@ -891,6 +891,9 @@ const GPUGraph = React.memo( onCheckedChange: (c) => { setUseAdvancedLabels(c); track('interactivity_advanced_labels_toggled', { enabled: c }); + // Parallelism labels are point labels; turning them on is + // pointless if labels are hidden, so auto-enable Labels. + if (c && !showPointLabels) setShowPointLabels(true); }, }, { diff --git a/packages/app/src/components/inference/ui/ScatterGraph.tsx b/packages/app/src/components/inference/ui/ScatterGraph.tsx index 68e9cc14..b7ee1937 100644 --- a/packages/app/src/components/inference/ui/ScatterGraph.tsx +++ b/packages/app/src/components/inference/ui/ScatterGraph.tsx @@ -152,8 +152,8 @@ const ScatterGraph = React.memo( selectedRunId, hideNonOptimal, setHideNonOptimal, - hidePointLabels, - setHidePointLabels, + showPointLabels, + setShowPointLabels, selectAllHwTypes, highContrast, setHighContrast, @@ -1486,7 +1486,7 @@ const ScatterGraph = React.memo( getCssColor(resolveColor(d.hwKey as string)), getOpacity: (d) => (isPointVisible(d) ? 1 : 0), getPointerEvents: (d) => (isPointVisible(d) ? 'auto' : 'none'), - hideLabels: hidePointLabels || showGradientLabels, + hideLabels: !showPointLabels || showGradientLabels, getLabelText: (d) => (useAdvancedLabels ? getPointLabel(d) : String(d.tp)), foreground: 'var(--foreground)', dataAttrs: { @@ -1585,7 +1585,7 @@ const ScatterGraph = React.memo( ); // Labels - const showLabels = !hidePointLabels && !showGradientLabels; + const showLabels = showPointLabels && !showGradientLabels; overlayPoints.each(function (d) { d3.select(this) .selectAll('.overlay-label') @@ -1864,7 +1864,7 @@ const ScatterGraph = React.memo( resolveColor, pointsData, isPointVisible, - hidePointLabels, + showPointLabels, useAdvancedLabels, buildPointConfigId, overlayData, @@ -2154,12 +2154,12 @@ const ScatterGraph = React.memo( }, }, { - id: 'scatter-hide-point-labels', - label: 'Hide Labels', - checked: hidePointLabels, + id: 'scatter-point-labels', + label: 'Labels', + checked: showPointLabels, onCheckedChange: (checked: boolean) => { - setHidePointLabels(checked); - track('latency_hide_point_labels_toggled', { enabled: checked }); + setShowPointLabels(checked); + track('latency_point_labels_toggled', { enabled: checked }); }, }, { @@ -2178,6 +2178,9 @@ const ScatterGraph = React.memo( onCheckedChange: (checked: boolean) => { setUseAdvancedLabels(checked); track('latency_advanced_labels_toggled', { enabled: checked }); + // Parallelism labels are point labels; turning them on is + // pointless if labels are hidden, so auto-enable Labels. + if (checked && !showPointLabels) setShowPointLabels(true); if (checked && !showGradientLabels) { window.dispatchEvent( new CustomEvent(GRADIENT_NUDGE_EVENT, { diff --git a/packages/app/src/lib/url-state.ts b/packages/app/src/lib/url-state.ts index aa7481ed..b558226b 100644 --- a/packages/app/src/lib/url-state.ts +++ b/packages/app/src/lib/url-state.ts @@ -30,6 +30,10 @@ const URL_STATE_KEYS = [ 'i_dstart', 'i_dend', 'i_optimal', + 'i_label', + // Legacy alias of `i_label` with inverted semantics — read-only on load so + // pre-rename share links (?i_nolabel=1) keep hiding point labels even if the + // default flips again later. New code only writes `i_label`. 'i_nolabel', 'i_hc', 'i_log', @@ -77,6 +81,7 @@ export const PARAM_DEFAULTS: Record = { i_dstart: '', i_dend: '', i_optimal: '', + i_label: '', i_nolabel: '', i_hc: '', i_log: '',