From e28392b7bd3c48ca56aca9943402c1618701965e Mon Sep 17 00:00:00 2001 From: ciregenz Date: Mon, 27 Jul 2026 20:35:28 -0700 Subject: [PATCH] [eric] settings: snap type onto the shared scale, 17 ad-hoc sizes down to 6 steps --- backend/tests/test_settings_type_scale.py | 57 +++++++++++++++++++ .../Settings/sections/SettingsHeader.tsx | 4 +- .../sections/general/DataPrivacySection.tsx | 10 ++-- .../sections/general/GeneralAdvanced.tsx | 2 +- .../sections/general/GeneralAgentDefaults.tsx | 18 +++--- .../sections/general/GeneralInterface.tsx | 8 +-- .../sections/general/SoftwareUpdateRow.tsx | 6 +- .../Settings/sections/models/ApiKeyCard.tsx | 4 +- .../sections/models/CustomProvidersEditor.tsx | 20 +++---- .../Settings/sections/models/ModelsTab.tsx | 4 +- .../pages/Settings/sections/settingsStyles.ts | 8 +-- .../sections/subscription/AccountCard.tsx | 16 +++--- .../subscription/OpenSwarmProCard.tsx | 24 ++++---- .../subscription/SubscriptionCard.tsx | 18 +++--- .../subscription/SubscriptionCards.tsx | 8 +-- .../Settings/sections/usage/UsageStats.tsx | 22 +++---- frontend/src/shared/styles/claudeTokens.ts | 31 +++++++++- 17 files changed, 173 insertions(+), 87 deletions(-) create mode 100644 backend/tests/test_settings_type_scale.py diff --git a/backend/tests/test_settings_type_scale.py b/backend/tests/test_settings_type_scale.py new file mode 100644 index 00000000..cf51766f --- /dev/null +++ b/backend/tests/test_settings_type_scale.py @@ -0,0 +1,57 @@ +"""Settings type has to stay on the shared scale. + +Measured 2026-07-27: the Settings area carried 17 distinct hardcoded rem sizes, including 0.72, +0.74, 0.75 and 0.78 in the same screen. Nobody chose those four; they are drift, and drift is what +makes a UI read as unfinished no matter how good any single page is. claudeTokens already defines +the scale and its own comment says to use `c.font.size.*` instead of a raw rem string, so this is a +standard the code had rather than a new opinion. + +The guard is here rather than in eslint because this suite is what actually runs on every change. +""" +import os +import re + +P_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +P_SETTINGS = os.path.join(P_REPO_ROOT, "frontend", "src", "app", "pages", "Settings") +P_TOKENS = os.path.join(P_REPO_ROOT, "frontend", "src", "shared", "styles", "claudeTokens.ts") + +P_RAW_SIZE_RE = re.compile(r"fontSize: '[0-9.]+rem'") + + +def p_settings_files(): + for dirpath, dirs, files in os.walk(P_SETTINGS): + for fn in sorted(files): + if fn.endswith((".tsx", ".ts")): + yield os.path.join(dirpath, fn) + + +def test_the_scale_exists_to_snap_to(): + """If the scale is ever removed, the rule below becomes unfollowable and this says so first.""" + with open(P_TOKENS, encoding="utf-8") as fh: + src = fh.read() + assert "export const fontSize: FontSizeScale" in src + assert "size: fontSize," in src, "the scale must be wired into the token objects, not just declared" + + +def test_no_hardcoded_rem_font_sizes_in_settings(): + """Every size goes through c.font.size.*, so a new page inherits the scale instead of guessing.""" + offenders = [] + for path in p_settings_files(): + with open(path, encoding="utf-8") as fh: + for i, line in enumerate(fh, 1): + if P_RAW_SIZE_RE.search(line): + offenders.append(f"{os.path.relpath(path, P_REPO_ROOT)}:{i}: {line.strip()}") + assert not offenders, ( + "hardcoded rem font sizes are back in Settings; use c.font.size.* instead:\n " + + "\n ".join(offenders)) + + +def test_settings_uses_a_small_number_of_steps(): + """A scale only buys anything if the page actually restrains itself to a few steps. Nine exist; + one screen reaching for most of them is the same noise problem wearing a nicer name.""" + used = set() + for path in p_settings_files(): + with open(path, encoding="utf-8") as fh: + used.update(re.findall(r"c\.font\.size\.([a-z]+)", fh.read())) + assert used, "Settings should reference the scale at all" + assert len(used) <= 7, f"Settings spreads across too many type steps: {sorted(used)}" diff --git a/frontend/src/app/pages/Settings/sections/SettingsHeader.tsx b/frontend/src/app/pages/Settings/sections/SettingsHeader.tsx index a012c273..3261f1fd 100644 --- a/frontend/src/app/pages/Settings/sections/SettingsHeader.tsx +++ b/frontend/src/app/pages/Settings/sections/SettingsHeader.tsx @@ -22,7 +22,7 @@ const SettingsHeader: React.FC<{ }} > - + Settings @@ -40,7 +40,7 @@ const SettingsHeader: React.FC<{ '& .MuiTab-root': { minHeight: 30, textTransform: 'none', - fontSize: '0.85rem', + fontSize: c.font.size.base, fontWeight: 500, color: c.text.muted, px: 1.75, diff --git a/frontend/src/app/pages/Settings/sections/general/DataPrivacySection.tsx b/frontend/src/app/pages/Settings/sections/general/DataPrivacySection.tsx index 99ad2355..ec161799 100644 --- a/frontend/src/app/pages/Settings/sections/general/DataPrivacySection.tsx +++ b/frontend/src/app/pages/Settings/sections/general/DataPrivacySection.tsx @@ -93,9 +93,9 @@ const DataPrivacySection: React.FC<{ borderRadius: 2.5, maxWidth: 360, }; - const titleSx = { color: c.text.primary, fontSize: '0.95rem', fontWeight: 600, mb: 1 }; - const bodySx = { color: c.text.secondary, fontSize: '0.8rem', lineHeight: 1.5, mb: 2 }; - const errSx = { color: c.status.error, fontSize: '0.75rem', mb: 1.5 }; + const titleSx = { color: c.text.primary, fontSize: c.font.size.md, fontWeight: 600, mb: 1 }; + const bodySx = { color: c.text.secondary, fontSize: c.font.size.sm, lineHeight: 1.5, mb: 2 }; + const errSx = { color: c.status.error, fontSize: c.font.size.xs, mb: 1.5 }; const cancelSx = { color: c.text.secondary, textTransform: 'none', fontWeight: 500 }; const actionRowSx = { display: 'flex', justifyContent: 'flex-end', gap: 1 }; @@ -104,7 +104,7 @@ const DataPrivacySection: React.FC<{ color: c.text.secondary, borderColor: c.border.medium, textTransform: 'none' as const, - fontSize: '0.8rem', + fontSize: c.font.size.sm, whiteSpace: 'nowrap' as const, '&:hover': { color: c.accent.primary, borderColor: c.accent.primary }, }; @@ -201,7 +201,7 @@ const DataPrivacySection: React.FC<{ size="small" autoFocus disabled={busy} - sx={{ mb: 2, '& .MuiOutlinedInput-root': { fontSize: '0.8rem' } }} + sx={{ mb: 2, '& .MuiOutlinedInput-root': { fontSize: c.font.size.sm } }} /> {err && {err}} diff --git a/frontend/src/app/pages/Settings/sections/general/GeneralAdvanced.tsx b/frontend/src/app/pages/Settings/sections/general/GeneralAdvanced.tsx index 42b4af45..1f3bc4ab 100644 --- a/frontend/src/app/pages/Settings/sections/general/GeneralAdvanced.tsx +++ b/frontend/src/app/pages/Settings/sections/general/GeneralAdvanced.tsx @@ -123,7 +123,7 @@ const GeneralAdvanced: React.FC<{ color: c.text.secondary, borderColor: c.border.medium, textTransform: 'none', - fontSize: '0.8rem', + fontSize: c.font.size.sm, whiteSpace: 'nowrap', '&:hover': { color: c.accent.primary, borderColor: c.accent.primary }, }} diff --git a/frontend/src/app/pages/Settings/sections/general/GeneralAgentDefaults.tsx b/frontend/src/app/pages/Settings/sections/general/GeneralAgentDefaults.tsx index d02e9658..4ebcc62a 100644 --- a/frontend/src/app/pages/Settings/sections/general/GeneralAgentDefaults.tsx +++ b/frontend/src/app/pages/Settings/sections/general/GeneralAgentDefaults.tsx @@ -49,7 +49,7 @@ const GeneralAgentDefaults: React.FC<{ sx={{ color: c.accent.primary, textTransform: 'none', - fontSize: '0.75rem', + fontSize: c.font.size.xs, py: 0.25, '&:hover': { bgcolor: `${c.accent.primary}10` }, }} @@ -72,7 +72,7 @@ const GeneralAgentDefaults: React.FC<{ sx={{ '& .MuiOutlinedInput-root': { fontFamily: c.font.mono, - fontSize: '0.8rem', + fontSize: c.font.size.sm, lineHeight: 1.6, color: c.text.secondary, }, @@ -110,7 +110,7 @@ const GeneralAgentDefaults: React.FC<{ textTransform: 'none', whiteSpace: 'nowrap', minWidth: 'auto', - fontSize: '0.8rem', + fontSize: c.font.size.sm, '&:hover': { color: c.accent.primary, borderColor: c.accent.primary }, }} > @@ -128,7 +128,7 @@ const GeneralAgentDefaults: React.FC<{ setForm({ ...form, default_mode: e.target.value })} - sx={{ fontSize: '0.85rem' }} + sx={{ fontSize: c.font.size.base }} MenuProps={{ PaperProps: { sx: { bgcolor: c.bg.surface, color: c.text.primary } } }} > {modesList.map((m) => ( @@ -228,7 +228,7 @@ const GeneralAgentDefaults: React.FC<{