From 308178beda19f92737f098095e161ca6d1658560 Mon Sep 17 00:00:00 2001
From: Linus Rath <139418639+rathlinus@users.noreply.github.com>
Date: Tue, 3 Nov 2026 05:06:26 +0100
Subject: [PATCH] fix: open each settings page at its top and keep the place in
the phone's page list
---
.../settings-phone-deep-link.test.tsx | 22 ++++++++++++++++++-
components/settings/settings-app.tsx | 20 ++++++++++++++---
2 files changed, 38 insertions(+), 4 deletions(-)
diff --git a/components/settings/__tests__/settings-phone-deep-link.test.tsx b/components/settings/__tests__/settings-phone-deep-link.test.tsx
index b65c4b437..2b0378e09 100644
--- a/components/settings/__tests__/settings-phone-deep-link.test.tsx
+++ b/components/settings/__tests__/settings-phone-deep-link.test.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { act, render, screen } from '@testing-library/react';
+import { act, fireEvent, render, screen } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
// On the phone the tab list is a screen of its own. A link to
@@ -86,4 +86,24 @@ describe('Settings deep links on the phone', () => {
expect(screen.getByTestId('settings-tab-appearance')).toBeTruthy();
expect(screen.queryByText('appearance pane')).toBeNull();
});
+
+ it('opens a page at its top and keeps the place in the list', () => {
+ window.history.replaceState(null, '', '/settings');
+ render();
+ const list = document.querySelector('main') as HTMLElement;
+ list.scrollTop = 400;
+ fireEvent.scroll(list);
+
+ fireEvent.click(screen.getByTestId('settings-tab-organize'));
+ expect(screen.getByText('organize pane')).toBeTruthy();
+ const page = document.querySelector('main') as HTMLElement;
+ expect(page.scrollTop).toBe(0);
+
+ page.scrollTop = 900;
+ act(() => {
+ window.dispatchEvent(new PopStateEvent('popstate'));
+ });
+ expect(screen.getByTestId('settings-tab-organize')).toBeTruthy();
+ expect((document.querySelector('main') as HTMLElement).scrollTop).toBe(400);
+ });
});
diff --git a/components/settings/settings-app.tsx b/components/settings/settings-app.tsx
index 93a658443..5ff290c4f 100644
--- a/components/settings/settings-app.tsx
+++ b/components/settings/settings-app.tsx
@@ -411,6 +411,11 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
});
const [isResizing, setIsResizing] = useState(false);
const dragStartWidth = useRef(256);
+ // The phone's page list keeps its place while a page is open on top of it.
+ const mobileListScrollTop = useRef(0);
+ const attachMobileList = useCallback((el: HTMLElement | null) => {
+ if (el) el.scrollTop = mobileListScrollTop.current;
+ }, []);
// Check auth on mount – skip when already authenticated so that navigating
// between routes doesn't retrigger checkAuth's transient `{ client: null,
@@ -920,7 +925,8 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
-
+ {/* A key per page: a page opens at its top, not at the scroll offset of the one before. */}
+
{renderTabContent()}
@@ -952,7 +958,15 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
-
+ { mobileListScrollTop.current = e.currentTarget.scrollTop; }}
+ embedded={isEmbedded}
+ label={t('title')}
+ className="flex-1 overflow-y-auto"
+ data-tour="settings-tabs"
+ >