From 3c229169fcabb3f95320a480f63ba96f046ecc5a Mon Sep 17 00:00:00 2001 From: "[._.]/ Adam Eivy" Date: Sun, 6 Sep 2026 06:21:11 +0000 Subject: [PATCH] persist model comparison chart settings to localStorage Filters, zoom, scale, and stretch settings lived only in the URL query string, so returning to the page without a matching link reset every setting. Restore the last-used settings from localStorage on a bare visit (an explicit URL still wins), and keep localStorage in sync as the user changes settings. --- .../src/components/models/ModelComparison.jsx | 22 +++++++++++++++++ .../models/ModelComparison.test.jsx | 24 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/client/src/components/models/ModelComparison.jsx b/client/src/components/models/ModelComparison.jsx index 79ef34508d..eebbe26aeb 100644 --- a/client/src/components/models/ModelComparison.jsx +++ b/client/src/components/models/ModelComparison.jsx @@ -33,6 +33,9 @@ import { import Modal from '../ui/Modal'; import ComparisonResearch from './ComparisonResearch'; import { EFFORT_LADDER, withEstimatedCosts } from '../../lib/effortCostEstimate'; +import { safeReadStorage, safeWriteStorage } from '../../lib/safeStorage'; + +const SETTINGS_STORAGE_KEY = 'portos-model-comparison-settings'; const COLORS = [ '#2563eb', // blue (GPT-5.6 Sol) @@ -99,6 +102,25 @@ export default function ModelComparison() { const [syncError, setSyncError] = useState(''); const [syncing, setSyncing] = useState(false); + // Restore the last-viewed settings from localStorage when the page is opened + // with no query string (a bookmark-free visit), so filters/zoom/scale persist + // across sessions instead of resetting every time. An explicit URL (a shared + // link, browser back/forward) always wins over the stored snapshot. + useEffect(() => { + if (params.toString() !== '') return; + const stored = safeReadStorage(SETTINGS_STORAGE_KEY); + if (!stored) return; + setParams(new URLSearchParams(stored), { replace: true }); + // Restore once, on mount only — subsequent param changes are the user + // driving the page, not something to overwrite from storage again. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + const query = params.toString(); + if (query) safeWriteStorage(SETTINGS_STORAGE_KEY, query); + }, [params]); + const xMinParam = params.get('xMin'); const xMaxParam = params.get('xMax'); const yMinParam = params.get('yMin'); diff --git a/client/src/components/models/ModelComparison.test.jsx b/client/src/components/models/ModelComparison.test.jsx index 534fd2ae62..48a2d41966 100644 --- a/client/src/components/models/ModelComparison.test.jsx +++ b/client/src/components/models/ModelComparison.test.jsx @@ -296,3 +296,27 @@ it('respects initial zoom and stretch URL search parameters', async () => { expect(screen.getByTestId('yaxis')).toHaveAttribute('data-domain', JSON.stringify([40, 60])); }); +it('persists settings to localStorage and restores them on a fresh visit', async () => { + localStorage.clear(); + const { unmount } = render( + + + + ); + await act(async () => {}); + await screen.findByText(/1 plotted/); + + fireEvent.click(screen.getByRole('button', { name: '1.5×' })); + await waitFor(() => expect(localStorage.getItem('portos-model-comparison-settings')).toContain('stretch=1.5')); + unmount(); + + render( + + + + ); + await act(async () => {}); + await screen.findByText(/1 plotted/); + expect(screen.getByRole('button', { name: '1.5×' })).toHaveAttribute('aria-pressed', 'true'); +}); +