From 39f7403e2b85ea81f82e418b9a715f1a614e7fcf Mon Sep 17 00:00:00 2001 From: adibarra <93070681+adibarra@users.noreply.github.com> Date: Thu, 19 Mar 2026 19:28:28 -0500 Subject: [PATCH 1/9] replace auto-populated changelog dates with opt-in intermediate date picker --- .../components/inference/ui/ChartControls.tsx | 36 ++++++++++++- .../components/inference/ui/ChartDisplay.tsx | 50 +++---------------- .../inference/ui/ComparisonChangelog.tsx | 23 +++------ .../hooks/api/use-comparison-changelogs.ts | 36 ++++++++++++- 4 files changed, 82 insertions(+), 63 deletions(-) diff --git a/packages/app/src/components/inference/ui/ChartControls.tsx b/packages/app/src/components/inference/ui/ChartControls.tsx index b027627..6726020 100644 --- a/packages/app/src/components/inference/ui/ChartControls.tsx +++ b/packages/app/src/components/inference/ui/ChartControls.tsx @@ -11,6 +11,7 @@ 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, @@ -68,9 +69,14 @@ const GROUPED_Y_AXIS_OPTIONS = (() => { 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 }: ChartControlsProps) { +export default function ChartControls({ + hideGpuComparison = false, + intermediateDates = [], +}: ChartControlsProps) { const { selectedModel, setSelectedModel, @@ -86,6 +92,8 @@ export default function ChartControls({ hideGpuComparison = false }: ChartContro availableGPUs, selectedDateRange, setSelectedDateRange, + selectedDates, + setSelectedDates, dateRangeAvailableDates, isCheckingAvailableDates, availablePrecisions, @@ -327,6 +335,32 @@ export default function ChartControls({ hideGpuComparison = false }: ChartContro /> )} + + {!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 e768ce8..375d194 100644 --- a/packages/app/src/components/inference/ui/ChartDisplay.tsx +++ b/packages/app/src/components/inference/ui/ChartDisplay.tsx @@ -1,7 +1,7 @@ 'use client'; import { track } from '@/lib/analytics'; import Link from 'next/link'; -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useMemo, useState } from 'react'; import { ChevronDown, X } from 'lucide-react'; import { useInference } from '@/components/inference/InferenceContext'; @@ -37,7 +37,6 @@ import { Sequence, } from '@/lib/data-mappings'; import { useComparisonChangelogs } from '@/hooks/api/use-comparison-changelogs'; -import { configKeyMatchesHwKey } from '@/components/inference/utils/changelogFormatters'; import { useTrendData } from '@/components/inference/hooks/useTrendData'; import ChartControls from './ChartControls'; @@ -113,8 +112,6 @@ export default function ChartDisplay() { selectedGPUs, selectedPrecisions, selectedDateRange, - setSelectedDates, - availableDates, dateRangeAvailableDates, selectedModel, selectedSequence, @@ -130,49 +127,15 @@ export default function ChartDisplay() { const { changelogs, + intermediateDates, loading: changelogsLoading, totalDatesQueried, - } = useComparisonChangelogs(selectedGPUs, selectedDateRange, availableDates); - - // Auto-populate intermediate dates from changelog dates so they appear on the GPU graph - const prevChangelogDatesRef = useRef(''); - useEffect(() => { - if (!selectedDateRange.startDate || !selectedDateRange.endDate || selectedGPUs.length === 0) - return; - const gpuDatesSet = new Set(dateRangeAvailableDates); - const precSet = new Set(selectedPrecisions); - const changelogDates = changelogs - .filter((c) => - c.entries.some((entry) => - entry.config_keys.some((key) => { - const precision = key.split('-')[1]; - return ( - precSet.has(precision) && selectedGPUs.some((gpu) => configKeyMatchesHwKey(key, gpu)) - ); - }), - ), - ) - .map((c) => c.date) - .filter( - (d) => - d !== selectedDateRange.startDate && - d !== selectedDateRange.endDate && - gpuDatesSet.has(d), - ) - .sort(); - const key = changelogDates.join(','); - if (key !== prevChangelogDatesRef.current) { - prevChangelogDatesRef.current = key; - setSelectedDates(changelogDates); - } - }, [ - changelogs, - selectedDateRange, + } = useComparisonChangelogs( selectedGPUs, selectedPrecisions, - setSelectedDates, + selectedDateRange, dateRangeAvailableDates, - ]); + ); const { unofficialRunInfo, getOverlayData, isUnofficialRun } = useUnofficialRun(); @@ -705,7 +668,7 @@ export default function ChartDisplay() { - + {selectedGPUs.length === 0 && } {selectedGPUs.length > 0 && @@ -715,7 +678,6 @@ export default function ChartDisplay() { changelogs={changelogs} selectedGPUs={selectedGPUs} selectedPrecisions={selectedPrecisions} - selectedDateRange={selectedDateRange} loading={changelogsLoading} totalDatesQueried={totalDatesQueried} /> diff --git a/packages/app/src/components/inference/ui/ComparisonChangelog.tsx b/packages/app/src/components/inference/ui/ComparisonChangelog.tsx index 440409b..3920d1a 100644 --- a/packages/app/src/components/inference/ui/ComparisonChangelog.tsx +++ b/packages/app/src/components/inference/ui/ComparisonChangelog.tsx @@ -17,7 +17,6 @@ interface ComparisonChangelogProps { changelogs: ComparisonChangelogType[]; selectedGPUs: string[]; selectedPrecisions: string[]; - selectedDateRange: { startDate: string; endDate: string }; loading?: boolean; totalDatesQueried: number; } @@ -26,7 +25,6 @@ export default function ComparisonChangelog({ changelogs, selectedGPUs, selectedPrecisions, - selectedDateRange, loading, totalDatesQueried, }: ComparisonChangelogProps) { @@ -51,20 +49,13 @@ export default function ComparisonChangelog({ .filter((item) => item.entries.length > 0); }, [changelogs, selectedGPUs, selectedPrecisions]); - // Always include start/end dates, even if they have no changelog entries - const sortedChangelogs = useMemo(() => { - const byDate = new Map(filteredChangelogs.map((c) => [c.date, c])); - const { startDate, endDate } = selectedDateRange; - if (startDate && !byDate.has(startDate)) { - byDate.set(startDate, { date: startDate, entries: [] }); - } - if (endDate && !byDate.has(endDate)) { - byDate.set(endDate, { date: endDate, entries: [] }); - } - return [...byDate.values()].sort( - (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(), - ); - }, [filteredChangelogs, selectedDateRange]); + const sortedChangelogs = useMemo( + () => + [...filteredChangelogs].sort( + (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(), + ), + [filteredChangelogs], + ); const handleToggle = () => { const newState = !isExpanded; diff --git a/packages/app/src/hooks/api/use-comparison-changelogs.ts b/packages/app/src/hooks/api/use-comparison-changelogs.ts index 2262818..8a272b5 100644 --- a/packages/app/src/hooks/api/use-comparison-changelogs.ts +++ b/packages/app/src/hooks/api/use-comparison-changelogs.ts @@ -2,6 +2,7 @@ import { useQueries } from '@tanstack/react-query'; import { useMemo } from 'react'; import { fetchWorkflowInfo, type ChangelogRow, type WorkflowInfoResponse } from '@/lib/api'; +import { configKeyMatchesHwKey } from '@/components/inference/utils/changelogFormatters'; export interface ComparisonChangelog { date: string; @@ -16,13 +17,14 @@ export interface ComparisonChangelog { export function useComparisonChangelogs( selectedGPUs: string[], + selectedPrecisions: string[], selectedDateRange: { startDate: string; endDate: string }, availableDates: string[], ) { const isComparisonMode = selectedGPUs.length > 0 && !!selectedDateRange.startDate && !!selectedDateRange.endDate; - // Query all available dates within the selected range + // Query all available dates within the selected range to discover which have changelog entries const datesInRange = useMemo(() => { if (!isComparisonMode) return []; return availableDates.filter( @@ -66,7 +68,37 @@ export function useComparisonChangelogs( return results; }, [isComparisonMode, datesInRange, queries]); + // Intermediate dates with changelog entries matching selected GPUs/precisions (excluding start/end) + const intermediateDates = useMemo(() => { + if (!isComparisonMode) return []; + const precSet = new Set(selectedPrecisions); + return changelogs + .filter( + (c) => + c.date !== selectedDateRange.startDate && + c.date !== selectedDateRange.endDate && + c.entries.some((entry) => + entry.config_keys.some((key) => { + const precision = key.split('-')[1]; + return ( + precSet.has(precision) && + selectedGPUs.some((gpu) => configKeyMatchesHwKey(key, gpu)) + ); + }), + ), + ) + .map((c) => c.date) + .sort(); + }, [ + isComparisonMode, + changelogs, + selectedGPUs, + selectedPrecisions, + selectedDateRange.startDate, + selectedDateRange.endDate, + ]); + const loading = queries.some((q) => q.isLoading); - return { changelogs, loading, totalDatesQueried: datesInRange.length }; + return { changelogs, intermediateDates, loading, totalDatesQueried: datesInRange.length }; } From 3b504c0715cd711a7830cf3d2c0efa00c86cfbb0 Mon Sep 17 00:00:00 2001 From: adibarra <93070681+adibarra@users.noreply.github.com> Date: Thu, 19 Mar 2026 19:35:00 -0500 Subject: [PATCH 2/9] show inherited changelog entries by walking backwards for uncovered GPUs --- .../components/inference/ui/ChartDisplay.tsx | 7 +- .../inference/ui/ComparisonChangelog.tsx | 96 ++++++++++++++----- .../hooks/api/use-comparison-changelogs.ts | 29 +----- 3 files changed, 77 insertions(+), 55 deletions(-) diff --git a/packages/app/src/components/inference/ui/ChartDisplay.tsx b/packages/app/src/components/inference/ui/ChartDisplay.tsx index 375d194..1268e9f 100644 --- a/packages/app/src/components/inference/ui/ChartDisplay.tsx +++ b/packages/app/src/components/inference/ui/ChartDisplay.tsx @@ -130,12 +130,7 @@ export default function ChartDisplay() { intermediateDates, loading: changelogsLoading, totalDatesQueried, - } = useComparisonChangelogs( - selectedGPUs, - selectedPrecisions, - selectedDateRange, - dateRangeAvailableDates, - ); + } = useComparisonChangelogs(selectedGPUs, selectedDateRange, dateRangeAvailableDates); const { unofficialRunInfo, getOverlayData, isUnofficialRun } = useUnofficialRun(); diff --git a/packages/app/src/components/inference/ui/ComparisonChangelog.tsx b/packages/app/src/components/inference/ui/ComparisonChangelog.tsx index 3920d1a..731f7bb 100644 --- a/packages/app/src/components/inference/ui/ComparisonChangelog.tsx +++ b/packages/app/src/components/inference/ui/ComparisonChangelog.tsx @@ -30,32 +30,73 @@ export default function ComparisonChangelog({ }: ComparisonChangelogProps) { const [isExpanded, setIsExpanded] = useState(false); - // Filter changelog entries to only show those matching selected GPUs and precisions + // Filter changelog entries to only show those matching selected GPUs and precisions. + // For GPUs without a direct entry on a date, walk backwards to the most recent prior changelog. const filteredChangelogs = useMemo(() => { const precSet = new Set(selectedPrecisions); + const sorted = [...changelogs].sort( + (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(), + ); - 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)) + const matchesGpu = (key: string) => { + const precision = key.split('-')[1]; + return precSet.has(precision) && selectedGPUs.some((gpu) => configKeyMatchesHwKey(key, gpu)); + }; + + return sorted + .map((item) => { + // Direct entries matching selected GPUs/precisions + const directEntries = item.entries.filter((entry) => entry.config_keys.some(matchesGpu)); + + // Find GPUs that have direct entries on this date + const coveredGPUs = new Set(); + for (const entry of directEntries) { + for (const key of entry.config_keys) { + if (!matchesGpu(key)) continue; + for (const gpu of selectedGPUs) { + if (configKeyMatchesHwKey(key, gpu)) coveredGPUs.add(gpu); + } + } + } + + // For uncovered GPUs, walk backwards through earlier dates + const inheritedEntries: { + config_keys: string[]; + description: string; + pr_link: string | null; + inheritedFromDate: string; + }[] = []; + + for (const gpu of selectedGPUs) { + if (coveredGPUs.has(gpu)) continue; + // Walk backwards through sorted changelogs before this date + for (let i = sorted.indexOf(item) - 1; i >= 0; i--) { + const prior = sorted[i]; + const match = prior.entries.find((entry) => + entry.config_keys.some((key) => matchesGpu(key) && configKeyMatchesHwKey(key, gpu)), ); - }), - ), - })) - .filter((item) => item.entries.length > 0); - }, [changelogs, selectedGPUs, selectedPrecisions]); + if (match) { + // Avoid duplicates if same entry covers multiple uncovered GPUs + if ( + !inheritedEntries.some( + (e) => e.inheritedFromDate === prior.date && e.description === match.description, + ) + ) { + inheritedEntries.push({ ...match, inheritedFromDate: prior.date }); + } + break; + } + } + } - const sortedChangelogs = useMemo( - () => - [...filteredChangelogs].sort( - (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(), - ), - [filteredChangelogs], - ); + return { + ...item, + entries: directEntries, + inheritedEntries, + }; + }) + .filter((item) => item.entries.length > 0 || item.inheritedEntries.length > 0); + }, [changelogs, selectedGPUs, selectedPrecisions]); const handleToggle = () => { const newState = !isExpanded; @@ -95,13 +136,13 @@ export default function ComparisonChangelog({ }`} >
- {sortedChangelogs.length === 0 ? ( + {filteredChangelogs.length === 0 ? (

No config changelog data matching the selected GPUs and precisions for this date range. Changelog tracking began Dec 30, 2025.

) : ( - sortedChangelogs.map((item) => ( + filteredChangelogs.map((item) => (
{item.date} @@ -144,6 +185,15 @@ export default function ComparisonChangelog({ {formatChangelogDescription(entry.description)}
))} + {item.inheritedEntries.map((entry, entryIndex) => ( +
+ (from {entry.inheritedFromDate}){' '} + {formatChangelogDescription(entry.description)} +
+ ))}
)) )} diff --git a/packages/app/src/hooks/api/use-comparison-changelogs.ts b/packages/app/src/hooks/api/use-comparison-changelogs.ts index 8a272b5..4df1391 100644 --- a/packages/app/src/hooks/api/use-comparison-changelogs.ts +++ b/packages/app/src/hooks/api/use-comparison-changelogs.ts @@ -2,7 +2,6 @@ import { useQueries } from '@tanstack/react-query'; import { useMemo } from 'react'; import { fetchWorkflowInfo, type ChangelogRow, type WorkflowInfoResponse } from '@/lib/api'; -import { configKeyMatchesHwKey } from '@/components/inference/utils/changelogFormatters'; export interface ComparisonChangelog { date: string; @@ -17,7 +16,6 @@ export interface ComparisonChangelog { export function useComparisonChangelogs( selectedGPUs: string[], - selectedPrecisions: string[], selectedDateRange: { startDate: string; endDate: string }, availableDates: string[], ) { @@ -68,35 +66,14 @@ export function useComparisonChangelogs( return results; }, [isComparisonMode, datesInRange, queries]); - // Intermediate dates with changelog entries matching selected GPUs/precisions (excluding start/end) + // Intermediate dates with any changelog entries (excluding start/end) const intermediateDates = useMemo(() => { if (!isComparisonMode) return []; - const precSet = new Set(selectedPrecisions); return changelogs - .filter( - (c) => - c.date !== selectedDateRange.startDate && - c.date !== selectedDateRange.endDate && - c.entries.some((entry) => - entry.config_keys.some((key) => { - const precision = key.split('-')[1]; - return ( - precSet.has(precision) && - selectedGPUs.some((gpu) => configKeyMatchesHwKey(key, gpu)) - ); - }), - ), - ) + .filter((c) => c.date !== selectedDateRange.startDate && c.date !== selectedDateRange.endDate) .map((c) => c.date) .sort(); - }, [ - isComparisonMode, - changelogs, - selectedGPUs, - selectedPrecisions, - selectedDateRange.startDate, - selectedDateRange.endDate, - ]); + }, [isComparisonMode, changelogs, selectedDateRange.startDate, selectedDateRange.endDate]); const loading = queries.some((q) => q.isLoading); From ff5b3890977d9e873ff44a71bb97ec148b60a434 Mon Sep 17 00:00:00 2001 From: adibarra <93070681+adibarra@users.noreply.github.com> Date: Thu, 19 Mar 2026 20:18:24 -0500 Subject: [PATCH 3/9] show changelog immediately when GPU selected, no date range required --- .../components/inference/ui/ChartDisplay.tsx | 20 +++++----- .../hooks/api/use-comparison-changelogs.ts | 40 +++++++++++-------- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/packages/app/src/components/inference/ui/ChartDisplay.tsx b/packages/app/src/components/inference/ui/ChartDisplay.tsx index 1268e9f..7450790 100644 --- a/packages/app/src/components/inference/ui/ChartDisplay.tsx +++ b/packages/app/src/components/inference/ui/ChartDisplay.tsx @@ -666,17 +666,15 @@ export default function ChartDisplay() { {selectedGPUs.length === 0 && } - {selectedGPUs.length > 0 && - selectedDateRange.startDate && - selectedDateRange.endDate && ( - - )} + {selectedGPUs.length > 0 && ( + + )}
diff --git a/packages/app/src/hooks/api/use-comparison-changelogs.ts b/packages/app/src/hooks/api/use-comparison-changelogs.ts index 4df1391..48aec9f 100644 --- a/packages/app/src/hooks/api/use-comparison-changelogs.ts +++ b/packages/app/src/hooks/api/use-comparison-changelogs.ts @@ -19,40 +19,46 @@ export function useComparisonChangelogs( selectedDateRange: { startDate: string; endDate: string }, availableDates: string[], ) { - const isComparisonMode = - selectedGPUs.length > 0 && !!selectedDateRange.startDate && !!selectedDateRange.endDate; + const hasGPUs = selectedGPUs.length > 0; + const hasDateRange = !!selectedDateRange.startDate && !!selectedDateRange.endDate; - // Query all available dates within the selected range to discover which have changelog entries - const datesInRange = useMemo(() => { - if (!isComparisonMode) return []; + // When GPUs selected: fetch all available dates. When date range also set: limit to range. + const datesToQuery = useMemo(() => { + if (!hasGPUs) return []; + if (!hasDateRange) return availableDates; return availableDates.filter( (d) => d >= selectedDateRange.startDate && d <= selectedDateRange.endDate, ); - }, [isComparisonMode, availableDates, selectedDateRange.startDate, selectedDateRange.endDate]); + }, [ + hasGPUs, + hasDateRange, + availableDates, + selectedDateRange.startDate, + selectedDateRange.endDate, + ]); const queries = useQueries({ - queries: datesInRange.map((date) => ({ + queries: datesToQuery.map((date) => ({ queryKey: ['workflow-info', date], queryFn: () => fetchWorkflowInfo(date), - enabled: isComparisonMode, + enabled: hasGPUs, })), }); const changelogs = useMemo(() => { - if (!isComparisonMode) return []; + if (!hasGPUs) return []; const results: ComparisonChangelog[] = []; - for (let i = 0; i < datesInRange.length; i++) { + for (let i = 0; i < datesToQuery.length; i++) { const query = queries[i]; if (!query.data) continue; const data = query.data as WorkflowInfoResponse; if (!data.changelogs || data.changelogs.length === 0) continue; - // Include all changelogs for this date (across all runs) results.push({ - date: datesInRange[i], + date: datesToQuery[i], headRef: data.changelogs[data.changelogs.length - 1]?.head_ref, runUrl: data.runs[data.runs.length - 1]?.html_url ?? undefined, entries: data.changelogs.map((c: ChangelogRow) => ({ @@ -64,18 +70,18 @@ export function useComparisonChangelogs( } return results; - }, [isComparisonMode, datesInRange, queries]); + }, [hasGPUs, datesToQuery, queries]); - // Intermediate dates with any changelog entries (excluding start/end) + // Intermediate dates with any changelog entries (excluding start/end when date range is set) const intermediateDates = useMemo(() => { - if (!isComparisonMode) return []; + if (!hasGPUs || !hasDateRange) return []; return changelogs .filter((c) => c.date !== selectedDateRange.startDate && c.date !== selectedDateRange.endDate) .map((c) => c.date) .sort(); - }, [isComparisonMode, changelogs, selectedDateRange.startDate, selectedDateRange.endDate]); + }, [hasGPUs, hasDateRange, changelogs, selectedDateRange.startDate, selectedDateRange.endDate]); const loading = queries.some((q) => q.isLoading); - return { changelogs, intermediateDates, loading, totalDatesQueried: datesInRange.length }; + return { changelogs, intermediateDates, loading, totalDatesQueried: datesToQuery.length }; } From 78aad1af80a7868978f902c11d710d08cc0d7f69 Mon Sep 17 00:00:00 2001 From: adibarra <93070681+adibarra@users.noreply.github.com> Date: Thu, 19 Mar 2026 20:19:23 -0500 Subject: [PATCH 4/9] expand changelog by default --- .../app/src/components/inference/ui/ComparisonChangelog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/components/inference/ui/ComparisonChangelog.tsx b/packages/app/src/components/inference/ui/ComparisonChangelog.tsx index 731f7bb..d9d6080 100644 --- a/packages/app/src/components/inference/ui/ComparisonChangelog.tsx +++ b/packages/app/src/components/inference/ui/ComparisonChangelog.tsx @@ -28,7 +28,7 @@ export default function ComparisonChangelog({ loading, totalDatesQueried, }: ComparisonChangelogProps) { - const [isExpanded, setIsExpanded] = useState(false); + const [isExpanded, setIsExpanded] = useState(true); // Filter changelog entries to only show those matching selected GPUs and precisions. // For GPUs without a direct entry on a date, walk backwards to the most recent prior changelog. From ea8fa0093ca979eab1efee2db05e5964651f520b Mon Sep 17 00:00:00 2001 From: adibarra <93070681+adibarra@users.noreply.github.com> Date: Thu, 19 Mar 2026 20:26:59 -0500 Subject: [PATCH 5/9] remove inherited changelog entries from display --- .../inference/ui/ComparisonChangelog.tsx | 85 +++---------------- 1 file changed, 14 insertions(+), 71 deletions(-) diff --git a/packages/app/src/components/inference/ui/ComparisonChangelog.tsx b/packages/app/src/components/inference/ui/ComparisonChangelog.tsx index d9d6080..882b1ee 100644 --- a/packages/app/src/components/inference/ui/ComparisonChangelog.tsx +++ b/packages/app/src/components/inference/ui/ComparisonChangelog.tsx @@ -30,72 +30,24 @@ export default function ComparisonChangelog({ }: ComparisonChangelogProps) { const [isExpanded, setIsExpanded] = useState(true); - // Filter changelog entries to only show those matching selected GPUs and precisions. - // For GPUs without a direct entry on a date, walk backwards to the most recent prior changelog. + // Filter changelog entries to only show those matching selected GPUs and precisions const filteredChangelogs = useMemo(() => { const precSet = new Set(selectedPrecisions); - const sorted = [...changelogs].sort( - (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(), - ); - const matchesGpu = (key: string) => { - const precision = key.split('-')[1]; - return precSet.has(precision) && selectedGPUs.some((gpu) => configKeyMatchesHwKey(key, gpu)); - }; - - return sorted - .map((item) => { - // Direct entries matching selected GPUs/precisions - const directEntries = item.entries.filter((entry) => entry.config_keys.some(matchesGpu)); - - // Find GPUs that have direct entries on this date - const coveredGPUs = new Set(); - for (const entry of directEntries) { - for (const key of entry.config_keys) { - if (!matchesGpu(key)) continue; - for (const gpu of selectedGPUs) { - if (configKeyMatchesHwKey(key, gpu)) coveredGPUs.add(gpu); - } - } - } - - // For uncovered GPUs, walk backwards through earlier dates - const inheritedEntries: { - config_keys: string[]; - description: string; - pr_link: string | null; - inheritedFromDate: string; - }[] = []; - - for (const gpu of selectedGPUs) { - if (coveredGPUs.has(gpu)) continue; - // Walk backwards through sorted changelogs before this date - for (let i = sorted.indexOf(item) - 1; i >= 0; i--) { - const prior = sorted[i]; - const match = prior.entries.find((entry) => - entry.config_keys.some((key) => matchesGpu(key) && configKeyMatchesHwKey(key, gpu)), + 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)) ); - if (match) { - // Avoid duplicates if same entry covers multiple uncovered GPUs - if ( - !inheritedEntries.some( - (e) => e.inheritedFromDate === prior.date && e.description === match.description, - ) - ) { - inheritedEntries.push({ ...match, inheritedFromDate: prior.date }); - } - break; - } - } - } - - return { - ...item, - entries: directEntries, - inheritedEntries, - }; - }) - .filter((item) => item.entries.length > 0 || item.inheritedEntries.length > 0); + }), + ), + })) + .filter((item) => item.entries.length > 0) + .sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); }, [changelogs, selectedGPUs, selectedPrecisions]); const handleToggle = () => { @@ -185,15 +137,6 @@ export default function ComparisonChangelog({ {formatChangelogDescription(entry.description)} ))} - {item.inheritedEntries.map((entry, entryIndex) => ( -
- (from {entry.inheritedFromDate}){' '} - {formatChangelogDescription(entry.description)} -
- ))} )) )} From 4da3e7d14add52525693afd76150772387da9a57 Mon Sep 17 00:00:00 2001 From: adibarra <93070681+adibarra@users.noreply.github.com> Date: Thu, 19 Mar 2026 20:44:05 -0500 Subject: [PATCH 6/9] use series date instead of actual DB date in GPU graph tooltip --- packages/app/src/components/inference/utils/tooltipUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/components/inference/utils/tooltipUtils.ts b/packages/app/src/components/inference/utils/tooltipUtils.ts index 2a39cf1..620fa48 100644 --- a/packages/app/src/components/inference/utils/tooltipUtils.ts +++ b/packages/app/src/components/inference/utils/tooltipUtils.ts @@ -235,7 +235,7 @@ export const generateGPUGraphTooltipContent = (config: TooltipConfig): string =>
${isPinned ? '
Click elsewhere to dismiss
' : ''}
- Date: ${d.actualDate ?? d.date} + Date: ${d.date}
GPU Config: ${hardwareConfig[d.hwKey] ? getDisplayLabel(hardwareConfig[d.hwKey]) : d.hwKey} From 4e074c69c517715076e0dabc4eae861610376aa2 Mon Sep 17 00:00:00 2001 From: adibarra <93070681+adibarra@users.noreply.github.com> Date: Thu, 19 Mar 2026 20:50:31 -0500 Subject: [PATCH 7/9] show series date in GPU tooltip with actual date when they differ --- packages/app/src/components/inference/utils/tooltipUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/components/inference/utils/tooltipUtils.ts b/packages/app/src/components/inference/utils/tooltipUtils.ts index 620fa48..b50fecd 100644 --- a/packages/app/src/components/inference/utils/tooltipUtils.ts +++ b/packages/app/src/components/inference/utils/tooltipUtils.ts @@ -235,7 +235,7 @@ export const generateGPUGraphTooltipContent = (config: TooltipConfig): string =>
${isPinned ? '
Click elsewhere to dismiss
' : ''}
- Date: ${d.date} + Date: ${d.date}${d.actualDate && d.actualDate !== d.date ? ` (data from ${d.actualDate})` : ''}
GPU Config: ${hardwareConfig[d.hwKey] ? getDisplayLabel(hardwareConfig[d.hwKey]) : d.hwKey} From 93fc7e24744bacbea04d16cf505e531c96508626 Mon Sep 17 00:00:00 2001 From: adibarra <93070681+adibarra@users.noreply.github.com> Date: Thu, 19 Mar 2026 21:03:18 -0500 Subject: [PATCH 8/9] use exact date matching for GPU comparison benchmark queries --- packages/app/src/app/api/v1/benchmarks/route.ts | 7 ++++--- .../src/components/inference/hooks/useChartData.ts | 4 +++- packages/app/src/hooks/api/use-benchmarks.ts | 11 ++++++++--- packages/app/src/lib/api.ts | 3 ++- packages/db/src/queries/benchmarks.ts | 6 +++++- 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/app/src/app/api/v1/benchmarks/route.ts b/packages/app/src/app/api/v1/benchmarks/route.ts index 50e5569..fb65640 100644 --- a/packages/app/src/app/api/v1/benchmarks/route.ts +++ b/packages/app/src/app/api/v1/benchmarks/route.ts @@ -9,9 +9,9 @@ import { cachedJson, cachedQuery } from '@/lib/api-cache'; export const dynamic = 'force-dynamic'; const getCachedBenchmarks = cachedQuery( - async (dbModelKey: string, date?: string) => { + async (dbModelKey: string, date?: string, exact?: boolean) => { const sql = getDb(); - return getLatestBenchmarks(sql, dbModelKey, date); + return getLatestBenchmarks(sql, dbModelKey, date, exact); }, 'benchmarks', { blobOnly: true }, @@ -21,13 +21,14 @@ export async function GET(request: NextRequest) { const params = request.nextUrl.searchParams; const model = params.get('model') ?? ''; const date = params.get('date') ?? undefined; + const exact = params.get('exact') === 'true'; const dbModelKey = DISPLAY_MODEL_TO_DB[model]; if (!dbModelKey) { return NextResponse.json({ error: 'Unknown model' }, { status: 400 }); } try { - const rows = await getCachedBenchmarks(dbModelKey, date); + const rows = await getCachedBenchmarks(dbModelKey, date, exact || undefined); return cachedJson(rows); } catch (error) { console.error('Error fetching benchmarks:', error); diff --git a/packages/app/src/components/inference/hooks/useChartData.ts b/packages/app/src/components/inference/hooks/useChartData.ts index 3c8854f..16d7717 100644 --- a/packages/app/src/components/inference/hooks/useChartData.ts +++ b/packages/app/src/components/inference/hooks/useChartData.ts @@ -100,7 +100,9 @@ export function useChartData( }, [selectedGPUs, selectedDates, selectedDateRange, selectedRunDate]); const comparisonQueries = useQueries({ - queries: comparisonDates.map((date) => benchmarkQueryOptions(selectedModel, date, enabled)), + queries: comparisonDates.map((date) => + benchmarkQueryOptions(selectedModel, date, enabled, true), + ), }); const comparisonLoading = comparisonQueries.some((q) => q.isLoading); diff --git a/packages/app/src/hooks/api/use-benchmarks.ts b/packages/app/src/hooks/api/use-benchmarks.ts index 1556bde..c20eda8 100644 --- a/packages/app/src/hooks/api/use-benchmarks.ts +++ b/packages/app/src/hooks/api/use-benchmarks.ts @@ -3,10 +3,15 @@ import { useQuery, keepPreviousData } from '@tanstack/react-query'; import { fetchBenchmarks } from '@/lib/api'; /** Shared query options — reused by useQueries for comparison dates. */ -export function benchmarkQueryOptions(model: string, date: string, enabled: boolean = true) { +export function benchmarkQueryOptions( + model: string, + date: string, + enabled: boolean = true, + exact?: boolean, +) { return { - queryKey: ['benchmarks', model, date] as const, - queryFn: () => fetchBenchmarks(model, date), + queryKey: ['benchmarks', model, date, exact ? 'exact' : 'latest'] as const, + queryFn: () => fetchBenchmarks(model, date, exact), enabled: enabled && Boolean(model), }; } diff --git a/packages/app/src/lib/api.ts b/packages/app/src/lib/api.ts index 107b774..63efd0e 100644 --- a/packages/app/src/lib/api.ts +++ b/packages/app/src/lib/api.ts @@ -94,9 +94,10 @@ async function fetchJson(url: string): Promise { return res.json(); } -export function fetchBenchmarks(model: string, date?: string) { +export function fetchBenchmarks(model: string, date?: string, exact?: boolean) { const params = new URLSearchParams({ model }); if (date) params.set('date', date); + if (exact) params.set('exact', 'true'); return fetchJson(`/api/v1/benchmarks?${params}`); } diff --git a/packages/db/src/queries/benchmarks.ts b/packages/db/src/queries/benchmarks.ts index edbeca0..59673c7 100644 --- a/packages/db/src/queries/benchmarks.ts +++ b/packages/db/src/queries/benchmarks.ts @@ -38,9 +38,13 @@ export async function getLatestBenchmarks( sql: NeonClient, modelKey: string, date?: string, + exact?: boolean, ): Promise { if (date) { // Date-filtered: use base table with DISTINCT ON (the view only has the absolute latest) + // exact=true: only return data from this exact date (for GPU comparison) + // exact=false (default): return latest data as of this date (for main chart) + const dateFilter = exact ? sql`br.date = ${date}::date` : sql`br.date <= ${date}::date`; const rows = await sql` SELECT DISTINCT ON (br.config_id, br.conc, br.isl, br.osl) c.hardware, @@ -71,7 +75,7 @@ export async function getLatestBenchmarks( JOIN latest_workflow_runs wr ON wr.id = br.workflow_run_id WHERE c.model = ${modelKey} AND br.error IS NULL - AND br.date <= ${date}::date + AND ${dateFilter} ORDER BY br.config_id, br.conc, br.isl, br.osl, br.date DESC `; return rows as unknown as BenchmarkRow[]; From f2786c1b75fa93858777e9496fa16f017bd1c813 Mon Sep 17 00:00:00 2001 From: adibarra <93070681+adibarra@users.noreply.github.com> Date: Thu, 19 Mar 2026 21:22:08 -0500 Subject: [PATCH 9/9] fix tests for exact date benchmark query param --- .../src/app/api/v1/benchmarks/route.test.ts | 19 +++++++++++++++++-- .../app/src/hooks/api/use-benchmarks.test.ts | 7 ++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/app/src/app/api/v1/benchmarks/route.test.ts b/packages/app/src/app/api/v1/benchmarks/route.test.ts index 6970358..4b7e574 100644 --- a/packages/app/src/app/api/v1/benchmarks/route.test.ts +++ b/packages/app/src/app/api/v1/benchmarks/route.test.ts @@ -52,7 +52,7 @@ describe('GET /api/v1/benchmarks', () => { expect(res.status).toBe(200); const body = await res.json(); expect(body).toEqual(mockRows); - expect(mockGetLatestBenchmarks).toHaveBeenCalledWith('mock-sql', 'dsr1', undefined); + expect(mockGetLatestBenchmarks).toHaveBeenCalledWith('mock-sql', 'dsr1', undefined, undefined); }); it('passes date param to query when provided', async () => { @@ -60,7 +60,22 @@ describe('GET /api/v1/benchmarks', () => { const res = await GET(req('/api/v1/benchmarks?model=DeepSeek-R1-0528&date=2026-03-01')); expect(res.status).toBe(200); - expect(mockGetLatestBenchmarks).toHaveBeenCalledWith('mock-sql', 'dsr1', '2026-03-01'); + expect(mockGetLatestBenchmarks).toHaveBeenCalledWith( + 'mock-sql', + 'dsr1', + '2026-03-01', + undefined, + ); + }); + + it('passes exact=true when query param set', async () => { + mockGetLatestBenchmarks.mockResolvedValueOnce([]); + + const res = await GET( + req('/api/v1/benchmarks?model=DeepSeek-R1-0528&date=2026-03-01&exact=true'), + ); + expect(res.status).toBe(200); + expect(mockGetLatestBenchmarks).toHaveBeenCalledWith('mock-sql', 'dsr1', '2026-03-01', true); }); it('returns 500 when query throws', async () => { diff --git a/packages/app/src/hooks/api/use-benchmarks.test.ts b/packages/app/src/hooks/api/use-benchmarks.test.ts index 250113e..7329896 100644 --- a/packages/app/src/hooks/api/use-benchmarks.test.ts +++ b/packages/app/src/hooks/api/use-benchmarks.test.ts @@ -5,7 +5,12 @@ import { benchmarkQueryOptions } from '@/hooks/api/use-benchmarks'; describe('benchmarkQueryOptions', () => { it('builds query key from model and date', () => { const opts = benchmarkQueryOptions('DeepSeek-R1-0528', '2026-03-01'); - expect(opts.queryKey).toEqual(['benchmarks', 'DeepSeek-R1-0528', '2026-03-01']); + expect(opts.queryKey).toEqual(['benchmarks', 'DeepSeek-R1-0528', '2026-03-01', 'latest']); + }); + + it('builds exact query key when exact=true', () => { + const opts = benchmarkQueryOptions('DeepSeek-R1-0528', '2026-03-01', true, true); + expect(opts.queryKey).toEqual(['benchmarks', 'DeepSeek-R1-0528', '2026-03-01', 'exact']); }); it('produces distinct keys for different models', () => {