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');
+});
+