From 358f3e5806220892b5ea967d4b1cb1900645f87f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Fri, 31 Jul 2026 03:12:44 +0800 Subject: [PATCH] fix(desktop): put the settings rail on paper so the tab meets its content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rounded tab's whole premise is that its paper fill runs unbroken into the view it opens onto. Settings was the one place that did not hold: the selected white tab sat directly on a grey panel and read as a card stranded on it. The cause is not in the tab strip. The settings rail is painted --color-surface-container-low, which resolves to the same --cc-s0 the strip's trough does, so the trough appeared to wrap around the corner and continue down the left of the page with the tab marooned in it. Every other page — chat, market, scheduled, trace, subagent — is already paper at the top, which is why only this one showed. So the rail moves onto paper and the existing border-r does the separating, which is what every other secondary panel in the app already does (the workbench, the diff split). The rail was the odd one out. That also fixes something nobody had flagged: the selected rail entry is --color-surface-hover, which against the old grey was 1.06:1 — a highlight you cannot see. On paper it is 1.15:1. ring-offset moves with it. It is a painted ring, not a transparent one, so naming the old fill would have drawn a grey collar around the focus ring on a white rail. Two alternatives rejected: --color-surface-container (#FAFAF8) measures 1.045:1 against paper — indistinguishable, so it buys a special case and nothing else; and having the tab's fill follow whatever page is below it would leave Settings' tab with nothing but an outline, dropping the selected state entirely. --- desktop/src/pages/Settings.tsx | 17 ++++++++++++--- desktop/src/pages/SettingsNavigation.test.tsx | 21 +++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/desktop/src/pages/Settings.tsx b/desktop/src/pages/Settings.tsx index ad95d8f3..8dda7bc3 100644 --- a/desktop/src/pages/Settings.tsx +++ b/desktop/src/pages/Settings.tsx @@ -231,8 +231,17 @@ export function Settings() { {/* Tab navigation */} {/* Narrow enough that the rail is not a gutter of dead space, wide enough that the longest label in any locale — the Japanese - "コンピューター操作" — still clears the truncation on TabButton. */} -
+ "コンピューター操作" — still clears the truncation on TabButton. + + Paper, separated by the rule, the way every other secondary panel + in the app is (the workbench, the diff split). It used to be + `--color-surface-container-low`, which is the same value the tab + strip's trough resolves to — so the Settings tab, the one tab whose + content this rail *is*, was the only selected tab in the app whose + paper fill met a different colour at its bottom edge. It read as a + white card stranded on a grey panel. Nothing else changed to fix + it: the tab is right, this was the odd one out. */} +
setActiveTab('providers')} /> setActiveTab('general')} /> @@ -296,7 +305,9 @@ function TabButton({ icon, label, active, onClick }: { icon: string; label: stri ref={ref} onClick={onClick} aria-current={active ? 'page' : undefined} - className={`w-full flex items-center gap-3 rounded-[var(--radius-md)] px-3 py-2 text-[13.5px] text-left transition-[background-color,color] duration-150 ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-surface-container-low)] ${ + // `ring-offset` has to name the rail's own fill — it is painted, not + // transparent, so it tracks whatever the rail is. + className={`w-full flex items-center gap-3 rounded-[var(--radius-md)] px-3 py-2 text-[13.5px] text-left transition-[background-color,color] duration-150 ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-surface)] ${ active ? 'bg-[var(--color-surface-hover)] text-[var(--color-text-primary)] font-medium' : 'text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)]' diff --git a/desktop/src/pages/SettingsNavigation.test.tsx b/desktop/src/pages/SettingsNavigation.test.tsx index 90d62b97..8ac684cb 100644 --- a/desktop/src/pages/SettingsNavigation.test.tsx +++ b/desktop/src/pages/SettingsNavigation.test.tsx @@ -74,4 +74,25 @@ describe('Settings section navigation', () => { expect(scrollIntoView).toHaveBeenCalledWith({ block: 'nearest' }) expect(useUIStore.getState().activeSettingsTab).toBe('diagnostics') }) + + it('keeps the rail on paper so the Settings tab meets its own content', () => { + render() + + const rail = screen.getByRole('button', { name: 'Providers' }) + .closest('div[class*="w-[220px]"]') + + // This rail is what sits directly under the Settings tab, and the tab is + // filled with paper precisely so its bottom edge runs unbroken into the + // view it opens onto. The rail used to be + // `--color-surface-container-low`, which resolves to the same value as the + // strip's trough — making Settings the one tab in the app whose paper met + // a different colour at its own bottom edge, a white card stranded on a + // grey panel. Paper plus the rule, the way the workbench and the diff + // split already do it. + expect(rail?.className).toContain('bg-[var(--color-surface)]') + expect(rail?.className).not.toContain('bg-[var(--color-surface-container-low)]') + // The rule is what separates the rail from the section beside it now, so + // it is load-bearing rather than trim. + expect(rail?.className).toContain('border-r') + }) })