diff --git a/packages/app/src/components/inference/ui/ChartControls.tsx b/packages/app/src/components/inference/ui/ChartControls.tsx
index d322202..48d3f6f 100644
--- a/packages/app/src/components/inference/ui/ChartControls.tsx
+++ b/packages/app/src/components/inference/ui/ChartControls.tsx
@@ -10,7 +10,6 @@ import {
} from '@/components/ui/chart-selectors';
import { DateRangePicker } from '@/components/ui/date-range-picker';
import { LabelWithTooltip } from '@/components/ui/label-with-tooltip';
-import { MultiDatePicker } from '@/components/ui/multi-date-picker';
import { MultiSelect } from '@/components/ui/multi-select';
import {
Select,
@@ -76,14 +75,9 @@ const GROUPED_Y_AXIS_OPTIONS = METRIC_GROUPS.map((group) => ({
interface ChartControlsProps {
/** Hide GPU Config selector and related date pickers (used by Historical Trends tab) */
hideGpuComparison?: boolean;
- /** Intermediate dates within the comparison range that have changelog entries */
- intermediateDates?: string[];
}
-export default function ChartControls({
- hideGpuComparison = false,
- intermediateDates = [],
-}: ChartControlsProps) {
+export default function ChartControls({ hideGpuComparison = false }: ChartControlsProps) {
const {
selectedModel,
setSelectedModel,
@@ -99,8 +93,6 @@ export default function ChartControls({
availableGPUs,
selectedDateRange,
setSelectedDateRange,
- selectedDates,
- setSelectedDates,
dateRangeAvailableDates,
isCheckingAvailableDates,
availablePrecisions,
@@ -345,32 +337,6 @@ export default function ChartControls({
/>
)}
-
- {!hideGpuComparison &&
- selectedGPUs.length > 0 &&
- selectedDateRange.startDate &&
- selectedDateRange.endDate &&
- intermediateDates.length > 0 && (
-
-
- {
- setSelectedDates(value);
- track('inference_intermediate_dates_selected', {
- dates: value.join(','),
- });
- }}
- availableDates={intermediateDates}
- maxDates={2}
- placeholder="Select intermediate dates"
- />
-
- )}
diff --git a/packages/app/src/components/inference/ui/ChartDisplay.tsx b/packages/app/src/components/inference/ui/ChartDisplay.tsx
index e799b2a..fb51267 100644
--- a/packages/app/src/components/inference/ui/ChartDisplay.tsx
+++ b/packages/app/src/components/inference/ui/ChartDisplay.tsx
@@ -111,6 +111,8 @@ export default function ChartDisplay() {
selectedE2eXAxisMetric,
selectedGPUs,
selectedPrecisions,
+ selectedDates,
+ setSelectedDates,
selectedDateRange,
dateRangeAvailableDates,
selectedModel,
@@ -127,7 +129,6 @@ export default function ChartDisplay() {
const {
changelogs,
- intermediateDates,
loading: changelogsLoading,
totalDatesQueried,
} = useComparisonChangelogs(selectedGPUs, selectedDateRange, dateRangeAvailableDates);
@@ -677,7 +678,7 @@ export default function ChartDisplay() {
-
+
{selectedGPUs.length === 0 && }
{selectedGPUs.length > 0 && (
@@ -687,6 +688,21 @@ export default function ChartDisplay() {
selectedPrecisions={selectedPrecisions}
loading={changelogsLoading}
totalDatesQueried={totalDatesQueried}
+ selectedDates={selectedDates}
+ selectedDateRange={selectedDateRange}
+ onAddDate={(date) => {
+ if (!selectedDates.includes(date)) {
+ setSelectedDates([...selectedDates, date]);
+ }
+ }}
+ onRemoveDate={(date) => {
+ setSelectedDates(selectedDates.filter((d) => d !== date));
+ }}
+ onAddAllDates={(dates) => {
+ const merged = [...new Set([...selectedDates, ...dates])];
+ setSelectedDates(merged);
+ }}
+ firstAvailableDate={dateRangeAvailableDates[0]}
/>
)}
diff --git a/packages/app/src/components/inference/ui/ComparisonChangelog.test.ts b/packages/app/src/components/inference/ui/ComparisonChangelog.test.ts
new file mode 100644
index 0000000..2f3c519
--- /dev/null
+++ b/packages/app/src/components/inference/ui/ComparisonChangelog.test.ts
@@ -0,0 +1,92 @@
+import { describe, expect, it } from 'vitest';
+
+/**
+ * Tests for the "add to chart" logic used in ComparisonChangelog.
+ * Verifies date filtering: which dates are on chart, which are addable.
+ */
+
+interface MockChangelog {
+ date: string;
+ entries: { config_keys: string[]; description: string; pr_link: string | null }[];
+}
+
+function computeDatesOnChart(
+ selectedDates: string[],
+ selectedDateRange: { startDate: string; endDate: string },
+): Set {
+ const set = new Set(selectedDates);
+ if (selectedDateRange.startDate) set.add(selectedDateRange.startDate);
+ if (selectedDateRange.endDate) set.add(selectedDateRange.endDate);
+ return set;
+}
+
+function computeAddableDates(
+ filteredChangelogs: MockChangelog[],
+ datesOnChart: Set,
+): string[] {
+ return filteredChangelogs.map((c) => c.date).filter((d) => !datesOnChart.has(d));
+}
+
+const changelogs: MockChangelog[] = [
+ {
+ date: '2026-01-15',
+ entries: [{ config_keys: ['dsr1-fp8-h200-sglang'], description: 'Update', pr_link: null }],
+ },
+ {
+ date: '2026-01-20',
+ entries: [{ config_keys: ['dsr1-fp8-h200-sglang'], description: 'Bump', pr_link: null }],
+ },
+ {
+ date: '2026-01-25',
+ entries: [{ config_keys: ['dsr1-fp8-h200-sglang'], description: 'Tweak', pr_link: null }],
+ },
+];
+
+describe('ComparisonChangelog add-to-chart logic', () => {
+ it('all dates are addable when none are selected', () => {
+ const onChart = computeDatesOnChart([], { startDate: '', endDate: '' });
+ const addable = computeAddableDates(changelogs, onChart);
+ expect(addable).toEqual(['2026-01-15', '2026-01-20', '2026-01-25']);
+ });
+
+ it('dates in selectedDates are marked as on chart', () => {
+ const onChart = computeDatesOnChart(['2026-01-15', '2026-01-20'], {
+ startDate: '',
+ endDate: '',
+ });
+ expect(onChart.has('2026-01-15')).toBe(true);
+ expect(onChart.has('2026-01-20')).toBe(true);
+ expect(onChart.has('2026-01-25')).toBe(false);
+ const addable = computeAddableDates(changelogs, onChart);
+ expect(addable).toEqual(['2026-01-25']);
+ });
+
+ it('range endpoints are marked as on chart', () => {
+ const onChart = computeDatesOnChart([], {
+ startDate: '2026-01-15',
+ endDate: '2026-01-25',
+ });
+ expect(onChart.has('2026-01-15')).toBe(true);
+ expect(onChart.has('2026-01-25')).toBe(true);
+ const addable = computeAddableDates(changelogs, onChart);
+ expect(addable).toEqual(['2026-01-20']);
+ });
+
+ it('addable excludes both selectedDates and range endpoints', () => {
+ const onChart = computeDatesOnChart(['2026-01-20'], {
+ startDate: '2026-01-15',
+ endDate: '2026-01-25',
+ });
+ const addable = computeAddableDates(changelogs, onChart);
+ expect(addable).toEqual([]);
+ });
+
+ it('returns empty addable when all dates are already on chart', () => {
+ const onChart = computeDatesOnChart(['2026-01-15', '2026-01-20', '2026-01-25'], {
+ startDate: '',
+ endDate: '',
+ });
+ const addable = computeAddableDates(changelogs, onChart);
+ expect(addable).toEqual([]);
+ });
+});
diff --git a/packages/app/src/components/inference/ui/ComparisonChangelog.tsx b/packages/app/src/components/inference/ui/ComparisonChangelog.tsx
index 882b1ee..d35b00c 100644
--- a/packages/app/src/components/inference/ui/ComparisonChangelog.tsx
+++ b/packages/app/src/components/inference/ui/ComparisonChangelog.tsx
@@ -1,6 +1,6 @@
'use client';
-import { ChevronDown, ChevronUp, FileText } from 'lucide-react';
+import { ChevronDown, ChevronUp, FileText, Lock, Minus, Plus } from 'lucide-react';
import { useMemo, useState } from 'react';
import { track } from '@/lib/analytics';
@@ -11,7 +11,8 @@ import {
configKeyMatchesHwKey,
formatChangelogDescription,
} from '@/components/inference/utils/changelogFormatters';
-import { updateRepoUrl } from '@/lib/utils';
+import { HARDWARE_CONFIG } from '@/lib/constants';
+import { getDisplayLabel, updateRepoUrl } from '@/lib/utils';
interface ComparisonChangelogProps {
changelogs: ComparisonChangelogType[];
@@ -19,6 +20,13 @@ interface ComparisonChangelogProps {
selectedPrecisions: string[];
loading?: boolean;
totalDatesQueried: number;
+ selectedDates: string[];
+ selectedDateRange: { startDate: string; endDate: string };
+ onAddDate: (date: string) => void;
+ onRemoveDate: (date: string) => void;
+ onAddAllDates: (dates: string[]) => void;
+ /** Earliest date the selected GPU config has benchmark data */
+ firstAvailableDate?: string;
}
export default function ComparisonChangelog({
@@ -27,28 +35,63 @@ export default function ComparisonChangelog({
selectedPrecisions,
loading,
totalDatesQueried,
+ selectedDates,
+ selectedDateRange,
+ onAddDate,
+ onRemoveDate,
+ onAddAllDates,
+ firstAvailableDate,
}: ComparisonChangelogProps) {
const [isExpanded, setIsExpanded] = useState(true);
- // Filter changelog entries to only show those matching selected GPUs and precisions
+ // Filter changelog entries to only show those matching selected GPUs and precisions.
+ // Always keep range endpoints and first appearance date visible.
+ const pinnedDates = useMemo(() => {
+ const set = new Set();
+ if (selectedDateRange.startDate) set.add(selectedDateRange.startDate);
+ if (selectedDateRange.endDate) set.add(selectedDateRange.endDate);
+ if (firstAvailableDate) set.add(firstAvailableDate);
+ return set;
+ }, [selectedDateRange, firstAvailableDate]);
+
const filteredChangelogs = useMemo(() => {
const precSet = new Set(selectedPrecisions);
- return changelogs
- .map((item) => ({
- ...item,
- entries: item.entries.filter((entry) =>
- entry.config_keys.some((key) => {
- const precision = key.split('-')[1];
- return (
- precSet.has(precision) && selectedGPUs.some((gpu) => configKeyMatchesHwKey(key, gpu))
- );
- }),
- ),
- }))
- .filter((item) => item.entries.length > 0)
+ const mapped = changelogs.map((item) => ({
+ ...item,
+ entries: item.entries.filter((entry) =>
+ entry.config_keys.some((key) => {
+ const precision = key.split('-')[1];
+ return (
+ precSet.has(precision) && selectedGPUs.some((gpu) => configKeyMatchesHwKey(key, gpu))
+ );
+ }),
+ ),
+ }));
+
+ // Ensure pinned dates are always present
+ for (const date of pinnedDates) {
+ if (!mapped.some((item) => item.date === date)) {
+ mapped.push({ date, entries: [] });
+ }
+ }
+
+ return mapped
+ .filter((item) => item.entries.length > 0 || pinnedDates.has(item.date))
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
- }, [changelogs, selectedGPUs, selectedPrecisions]);
+ }, [changelogs, selectedGPUs, selectedPrecisions, pinnedDates]);
+
+ const datesOnChart = useMemo(() => {
+ const set = new Set(selectedDates);
+ if (selectedDateRange.startDate) set.add(selectedDateRange.startDate);
+ if (selectedDateRange.endDate) set.add(selectedDateRange.endDate);
+ return set;
+ }, [selectedDates, selectedDateRange]);
+
+ const addableDates = useMemo(
+ () => filteredChangelogs.map((c) => c.date).filter((d) => !datesOnChart.has(d)),
+ [filteredChangelogs, datesOnChart],
+ );
const handleToggle = () => {
const newState = !isExpanded;
@@ -65,22 +108,35 @@ export default function ComparisonChangelog({
return (
-