[eric] settings: snap type onto the shared scale, 17 ad-hoc sizes down to 6 steps

This commit is contained in:
ciregenz
2026-07-27 20:35:28 -07:00
parent 59b07cd575
commit e28392b7bd
17 changed files with 173 additions and 87 deletions
+57
View File
@@ -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)}"
@@ -22,7 +22,7 @@ const SettingsHeader: React.FC<{
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', pt: 1.5, pb: 0.5 }}>
<Typography sx={{ color: c.text.primary, fontWeight: 600, fontSize: '1rem' }}>
<Typography sx={{ color: c.text.primary, fontWeight: 600, fontSize: c.font.size.md }}>
Settings
</Typography>
<IconButton onClick={onClose} size="small" data-onboarding="settings-close-button" sx={{ color: c.text.tertiary, '&:hover': { color: c.text.primary } }}>
@@ -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,
@@ -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 && <Typography sx={errSx}>{err}</Typography>}
<Box sx={actionRowSx}>
@@ -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 },
}}
@@ -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<{
<Select
value={form.default_model}
onChange={(e) => setForm({ ...form, default_model: e.target.value })}
sx={{ fontSize: '0.85rem' }}
sx={{ fontSize: c.font.size.base }}
MenuProps={{ PaperProps: { sx: { bgcolor: c.bg.surface, color: c.text.primary } } }}
renderValue={(val) => {
const m = modelOptions.flat.find((x) => x.value === val);
@@ -136,7 +136,7 @@ const GeneralAgentDefaults: React.FC<{
return (
<Box component="span" sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.75 }}>
<span>{m.label}</span>
<Typography component="span" sx={{ fontSize: '0.65rem', color: c.text.ghost }}>
<Typography component="span" sx={{ fontSize: c.font.size.micro, color: c.text.ghost }}>
· {m.provider}
</Typography>
</Box>
@@ -171,7 +171,7 @@ const GeneralAgentDefaults: React.FC<{
/>
<Typography
sx={{
fontSize: '0.68rem',
fontSize: c.font.size.xxs,
fontWeight: 700,
letterSpacing: '0.08em',
textTransform: 'uppercase',
@@ -190,7 +190,7 @@ const GeneralAgentDefaults: React.FC<{
</Box>
</ListSubheader>,
...models.map((m) => (
<MenuItem key={m.value} value={m.value} sx={{ fontSize: '0.85rem', pl: 3 }}>
<MenuItem key={m.value} value={m.value} sx={{ fontSize: c.font.size.base, pl: 3 }}>
{m.label}
</MenuItem>
)),
@@ -209,7 +209,7 @@ const GeneralAgentDefaults: React.FC<{
<Select
value={form.default_mode}
onChange={(e) => 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<{
<Select
value={form.default_thinking_level}
onChange={(e) => setForm({ ...form, default_thinking_level: e.target.value as AppSettings['default_thinking_level'] })}
sx={{ fontSize: '0.85rem' }}
sx={{ fontSize: c.font.size.base }}
MenuProps={{ PaperProps: { sx: { bgcolor: c.bg.surface, color: c.text.primary } } }}
>
<MenuItem value="auto">Auto</MenuItem>
@@ -46,7 +46,7 @@ const GeneralInterface: React.FC<{
px: 2,
py: 0.5,
gap: 0.5,
fontSize: '0.8rem',
fontSize: c.font.size.sm,
'&.Mui-selected': {
bgcolor: `${c.accent.primary}15`,
color: c.accent.primary,
@@ -85,7 +85,7 @@ const GeneralInterface: React.FC<{
]}
sx={{
color: c.accent.primary,
'& .MuiSlider-markLabel': { color: c.text.tertiary, fontSize: '0.7rem' },
'& .MuiSlider-markLabel': { color: c.text.tertiary, fontSize: c.font.size.xxs },
'& .MuiSlider-valueLabel': { bgcolor: c.accent.primary },
}}
/>
@@ -130,11 +130,11 @@ const GeneralInterface: React.FC<{
>
<KeyboardIcon sx={{ fontSize: 16, color: recordingShortcut ? c.accent.primary : c.text.tertiary }} />
{recordingShortcut ? (
<Typography sx={{ fontSize: '0.8rem', color: c.accent.primary, fontWeight: 500 }}>
<Typography sx={{ fontSize: c.font.size.sm, color: c.accent.primary, fontWeight: 500 }}>
Press shortcut
</Typography>
) : (
<Typography sx={{ fontSize: '0.8rem', color: c.text.primary, fontFamily: c.font.mono, fontWeight: 500 }}>
<Typography sx={{ fontSize: c.font.size.sm, color: c.text.primary, fontFamily: c.font.mono, fontWeight: 500 }}>
{form.new_agent_shortcut
.split('+')
.map((p) => {
@@ -87,7 +87,7 @@ const SoftwareUpdateRow: React.FC<{ styles: SettingsStyles }> = ({ styles }) =>
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 },
}}
@@ -105,7 +105,7 @@ const SoftwareUpdateRow: React.FC<{ styles: SettingsStyles }> = ({ styles }) =>
color: c.accent.primary,
borderColor: c.accent.primary,
textTransform: 'none',
fontSize: '0.8rem',
fontSize: c.font.size.sm,
whiteSpace: 'nowrap',
'&:hover': { bgcolor: `${c.accent.primary}10` },
}}
@@ -127,7 +127,7 @@ const SoftwareUpdateRow: React.FC<{ styles: SettingsStyles }> = ({ styles }) =>
'&:hover': { bgcolor: c.accent.pressed },
'&.Mui-disabled': { bgcolor: c.accent.primary, color: '#fff', opacity: 0.7 },
textTransform: 'none',
fontSize: '0.8rem',
fontSize: c.font.size.sm,
whiteSpace: 'nowrap',
borderRadius: 1.5,
}}
@@ -44,7 +44,7 @@ const ApiKeyCard: React.FC<{
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography sx={labelSx}>{config.label}</Typography>
{value ? (
<Typography sx={{ fontSize: '0.6rem', fontWeight: 600, color: c.status.success, bgcolor: `${c.status.success}15`, px: 0.75, py: 0.15, borderRadius: '3px' }}>CONNECTED</Typography>
<Typography sx={{ fontSize: c.font.size.micro, fontWeight: 600, color: c.status.success, bgcolor: `${c.status.success}15`, px: 0.75, py: 0.15, borderRadius: '3px' }}>CONNECTED</Typography>
) : null}
</Box>
<Typography sx={{ ...descSx, mb: 1 }}>{config.desc}</Typography>
@@ -72,7 +72,7 @@ const ApiKeyCard: React.FC<{
href={config.href}
target="_blank"
rel="noopener"
sx={{ color: c.accent.primary, fontSize: '0.72rem', whiteSpace: 'nowrap', textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 0.3, '&:hover': { textDecoration: 'underline' } }}
sx={{ color: c.accent.primary, fontSize: c.font.size.xs, whiteSpace: 'nowrap', textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 0.3, '&:hover': { textDecoration: 'underline' } }}
>
Get key <OpenInNewIcon sx={{ fontSize: 11 }} />
</Typography>
@@ -35,7 +35,7 @@ const CustomProvidersEditor: React.FC<{
const allReady = readyCount === list.length;
return (
<Typography sx={{
fontSize: '0.6rem',
fontSize: c.font.size.micro,
fontWeight: 600,
color: allReady ? c.status.success : c.status.warning,
bgcolor: allReady ? `${c.status.success}15` : `${c.status.warning}1F`,
@@ -105,7 +105,7 @@ const CustomProvidersEditor: React.FC<{
>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: -0.25 }}>
<Typography sx={{
fontSize: '0.62rem',
fontSize: c.font.size.micro,
fontWeight: 700,
letterSpacing: '0.05em',
textTransform: 'uppercase' as const,
@@ -147,7 +147,7 @@ const CustomProvidersEditor: React.FC<{
required
error={dupeNameWithEarlier}
helperText={dupeNameWithEarlier ? 'Name must be unique' : undefined}
InputLabelProps={{ shrink: true, sx: { fontSize: '0.72rem', color: c.text.tertiary } }}
InputLabelProps={{ shrink: true, sx: { fontSize: c.font.size.xs, color: c.text.tertiary } }}
sx={fieldSx}
/>
<TextField
@@ -158,7 +158,7 @@ const CustomProvidersEditor: React.FC<{
placeholder="https://ollama.com/v1"
label="Base URL"
required
InputLabelProps={{ shrink: true, sx: { fontSize: '0.72rem', color: c.text.tertiary } }}
InputLabelProps={{ shrink: true, sx: { fontSize: c.font.size.xs, color: c.text.tertiary } }}
sx={{ ...fieldSx, '& .MuiOutlinedInput-root': { ...fieldSx['& .MuiOutlinedInput-root'], fontFamily: c.font.mono } }}
/>
<TextField
@@ -169,7 +169,7 @@ const CustomProvidersEditor: React.FC<{
fullWidth
placeholder="Leave blank for local servers (LM Studio, Ollama, ...)"
label="API Key (optional)"
InputLabelProps={{ shrink: true, sx: { fontSize: '0.72rem', color: c.text.tertiary } }}
InputLabelProps={{ shrink: true, sx: { fontSize: c.font.size.xs, color: c.text.tertiary } }}
sx={{ ...fieldSx, '& .MuiOutlinedInput-root': { ...fieldSx['& .MuiOutlinedInput-root'], fontFamily: c.font.mono } }}
InputProps={{
endAdornment: (
@@ -184,11 +184,11 @@ const CustomProvidersEditor: React.FC<{
</Box>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5, mt: 0.25 }}>
<Typography sx={{ fontSize: '0.65rem', fontWeight: 600, color: c.text.tertiary, textTransform: 'uppercase' as const, letterSpacing: '0.05em' }}>
<Typography sx={{ fontSize: c.font.size.micro, fontWeight: 600, color: c.text.tertiary, textTransform: 'uppercase' as const, letterSpacing: '0.05em' }}>
Models
</Typography>
{((cp.models || []).length === 0) ? (
<Typography sx={{ fontSize: '0.7rem', color: c.text.muted, fontStyle: 'italic', px: 0.5 }}>
<Typography sx={{ fontSize: c.font.size.xxs, color: c.text.muted, fontStyle: 'italic', px: 0.5 }}>
No models yet, add the model IDs this endpoint serves.
</Typography>
) : (
@@ -200,7 +200,7 @@ const CustomProvidersEditor: React.FC<{
size="small"
fullWidth
placeholder="e.g. gpt-oss:120b"
sx={{ ...fieldSx, '& .MuiOutlinedInput-root': { ...fieldSx['& .MuiOutlinedInput-root'], fontFamily: c.font.mono, fontSize: '0.78rem' } }}
sx={{ ...fieldSx, '& .MuiOutlinedInput-root': { ...fieldSx['& .MuiOutlinedInput-root'], fontFamily: c.font.mono, fontSize: c.font.size.xs } }}
/>
<IconButton
onClick={() => removeModel(mIdx)}
@@ -224,7 +224,7 @@ const CustomProvidersEditor: React.FC<{
mt: 0.25,
textTransform: 'none',
color: c.accent.primary,
fontSize: '0.72rem',
fontSize: c.font.size.xs,
minWidth: 'auto',
px: 0.75,
py: 0.25,
@@ -254,7 +254,7 @@ const CustomProvidersEditor: React.FC<{
color: c.text.primary,
borderColor: c.border.medium,
borderStyle: 'dashed',
fontSize: '0.78rem',
fontSize: c.font.size.xs,
px: 1.5,
py: 0.6,
'&:hover': { borderColor: c.accent.primary, bgcolor: `${c.accent.primary}08` },
@@ -22,7 +22,7 @@ const ModelsTab: React.FC<{
<Box sx={{ display: 'flex', flexDirection: 'column', pt: 2.5, pb: 1, gap: 2.5, animation: 'fadeIn 0.2s ease', '@keyframes fadeIn': { from: { opacity: 0 }, to: { opacity: 1 } } }}>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2.5 }}>
<Typography sx={{ fontSize: '0.7rem', color: c.text.ghost, textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600 }}>
<Typography sx={{ fontSize: c.font.size.xxs, color: c.text.ghost, textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600 }}>
Connect a Subscription
</Typography>
@@ -41,7 +41,7 @@ const ModelsTab: React.FC<{
</Box>
<Box data-onboarding="settings-api-keys" sx={{ display: 'flex', flexDirection: 'column', gap: 2.5 }}>
<Typography sx={{ fontSize: '0.7rem', color: c.text.ghost, textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600, mt: 1 }}>
<Typography sx={{ fontSize: c.font.size.xxs, color: c.text.ghost, textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600, mt: 1 }}>
Or Connect With API Keys
</Typography>
@@ -2,12 +2,12 @@
export function makeSettingsStyles(c: any) {
const fieldSx = {
'& .MuiOutlinedInput-root': {
fontSize: '0.85rem',
fontSize: c.font.size.base,
},
};
const sectionSx = {
fontSize: '0.7rem',
fontSize: c.font.size.xxs,
fontWeight: 600,
letterSpacing: '0.06em',
textTransform: 'uppercase' as const,
@@ -42,13 +42,13 @@ export function makeSettingsStyles(c: any) {
const labelSx = {
color: c.text.primary,
fontWeight: 500,
fontSize: '0.875rem',
fontSize: c.font.size.base,
lineHeight: 1.4,
};
const descSx = {
color: c.text.tertiary,
fontSize: '0.75rem',
fontSize: c.font.size.xs,
lineHeight: 1.4,
};
@@ -60,8 +60,8 @@ const AccountCard: React.FC = () => {
if (!userId && !hasBearer) {
return (
<Box sx={{ p: 2, mb: 2, borderRadius: `${c.radius.lg}px`, border: `1px solid ${c.border.subtle}`, bgcolor: c.bg.surface }}>
<Typography sx={{ fontSize: '0.85rem', color: c.text.primary, mb: 0.5 }}>Not signed in</Typography>
<Typography sx={{ fontSize: '0.78rem', color: c.text.muted, mb: 1.25 }}>
<Typography sx={{ fontSize: c.font.size.base, color: c.text.primary, mb: 0.5 }}>Not signed in</Typography>
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.muted, mb: 1.25 }}>
Sign in to sync settings across devices and back up your data.
</Typography>
<Button
@@ -70,7 +70,7 @@ const AccountCard: React.FC = () => {
onClick={() => setSignInOpen(true)}
sx={{
textTransform: 'none',
fontSize: '0.8rem',
fontSize: c.font.size.sm,
borderColor: c.border.medium,
color: c.text.primary,
'&:hover': { borderColor: c.accent.primary, color: c.accent.primary, bgcolor: 'transparent' },
@@ -88,14 +88,14 @@ const AccountCard: React.FC = () => {
<Box sx={{ p: 2, mb: 2, borderRadius: `${c.radius.lg}px`, border: `1px solid ${c.border.subtle}`, bgcolor: c.bg.surface }}>
<Box sx={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 2 }}>
<Box sx={{ minWidth: 0, flex: 1 }}>
<Typography sx={{ fontSize: '0.9rem', fontWeight: 600, color: c.text.primary, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
<Typography sx={{ fontSize: c.font.size.base, fontWeight: 600, color: c.text.primary, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{userEmail || 'Signed in'}
</Typography>
{methodLabel && (
<Typography sx={{ fontSize: '0.72rem', color: c.text.muted, mt: 0.25 }}>{methodLabel}</Typography>
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.muted, mt: 0.25 }}>{methodLabel}</Typography>
)}
{!userId && hasBearer && (
<Typography sx={{ fontSize: '0.72rem', color: c.text.muted, mt: 0.25 }}>
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.muted, mt: 0.25 }}>
Subscription connected. Sign in to also link this device to your account.
</Typography>
)}
@@ -108,7 +108,7 @@ const AccountCard: React.FC = () => {
onClick={onSignIn}
sx={{
textTransform: 'none',
fontSize: '0.75rem',
fontSize: c.font.size.xs,
borderColor: c.border.medium,
color: c.text.primary,
'&:hover': { borderColor: c.accent.primary, color: c.accent.primary, bgcolor: 'transparent' },
@@ -124,7 +124,7 @@ const AccountCard: React.FC = () => {
disabled={signingOut}
sx={{
textTransform: 'none',
fontSize: '0.75rem',
fontSize: c.font.size.xs,
color: c.text.muted,
'&:hover': { color: c.status.error, bgcolor: 'transparent' },
}}
@@ -139,16 +139,16 @@ const OpenSwarmProCard: React.FC = () => {
<Box sx={{ width: 8, height: 8, borderRadius: '50%', flexShrink: 0, bgcolor: c.border.medium }} />
<Box sx={{ minWidth: 0 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.7 }}>
<Typography sx={{ fontSize: '0.78rem', fontWeight: 600, color: c.text.primary }}>
<Typography sx={{ fontSize: c.font.size.xs, fontWeight: 600, color: c.text.primary }}>
OpenSwarm Pro
</Typography>
<Box sx={{ px: 0.7, py: 0.15, borderRadius: 999, bgcolor: `${c.accent.primary}12` }}>
<Typography sx={{ fontSize: '0.58rem', color: c.accent.primary, fontWeight: 600, letterSpacing: '0.03em' }}>
<Typography sx={{ fontSize: c.font.size.micro, color: c.accent.primary, fontWeight: 600, letterSpacing: '0.03em' }}>
RECOMMENDED
</Typography>
</Box>
</Box>
<Typography noWrap sx={{ fontSize: '0.65rem', color: c.text.muted }}>
<Typography noWrap sx={{ fontSize: c.font.size.micro, color: c.text.muted }}>
{copy.desc}
</Typography>
</Box>
@@ -158,7 +158,7 @@ const OpenSwarmProCard: React.FC = () => {
variant="contained"
size="small"
sx={{
textTransform: 'none', fontSize: '0.7rem', minWidth: 70,
textTransform: 'none', fontSize: c.font.size.xxs, minWidth: 70,
borderRadius: `${c.radius.md}px`,
bgcolor: c.accent.primary, boxShadow: 'none',
'&:hover': { bgcolor: c.accent.hover, boxShadow: 'none' },
@@ -218,7 +218,7 @@ const OpenSwarmProCard: React.FC = () => {
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1.5 }}>
<Typography sx={{ fontSize: '0.95rem', fontWeight: 600, color: c.text.primary }}>
<Typography sx={{ fontSize: c.font.size.md, fontWeight: 600, color: c.text.primary }}>
OpenSwarm Pro
</Typography>
<Box
@@ -236,7 +236,7 @@ const OpenSwarmProCard: React.FC = () => {
px: 1.2, py: 0.6, mb: 1.2, borderRadius: `${c.radius.sm}px`,
bgcolor: `${c.status.warning}15`, border: `1px solid ${c.status.warning}40`,
}}>
<Typography sx={{ fontSize: '0.72rem', color: c.status.warning, fontWeight: 500 }}>
<Typography sx={{ fontSize: c.font.size.xs, color: c.status.warning, fontWeight: 500 }}>
Subscription canceled. You still have access until {expiresLabel || 'the end of the period'}.
</Typography>
</Box>
@@ -245,10 +245,10 @@ const OpenSwarmProCard: React.FC = () => {
{/* Usage bar; percentage only, no raw counts. */}
<Box sx={{ mb: 1.2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', mb: 0.5 }}>
<Typography sx={{ fontSize: '0.78rem', color: c.text.secondary, fontWeight: 500 }}>
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.secondary, fontWeight: 500 }}>
Current usage
</Typography>
<Typography sx={{ fontSize: '0.72rem', color: c.text.muted }}>
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.muted }}>
{pct}% used
</Typography>
</Box>
@@ -266,7 +266,7 @@ const OpenSwarmProCard: React.FC = () => {
}}
/>
{windowEndsAt && (
<Typography sx={{ fontSize: '0.68rem', color: c.text.muted, mt: 0.4 }}>
<Typography sx={{ fontSize: c.font.size.xxs, color: c.text.muted, mt: 0.4 }}>
Resets {(() => {
const diff = windowEndsAt - Date.now();
if (diff <= 0) return 'soon';
@@ -279,7 +279,7 @@ const OpenSwarmProCard: React.FC = () => {
)}
</Box>
{expiresLabel && !isCanceled && (
<Typography sx={{ fontSize: '0.72rem', color: c.text.muted, mb: 1.5 }}>
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.muted, mb: 1.5 }}>
Renews on {expiresLabel}
</Typography>
)}
@@ -289,7 +289,7 @@ const OpenSwarmProCard: React.FC = () => {
onClick={() => setPickerOpen(true)}
size="small"
variant="contained"
sx={{ textTransform: 'none', fontSize: '0.78rem', borderRadius: `${c.radius.md}px` }}
sx={{ textTransform: 'none', fontSize: c.font.size.xs, borderRadius: `${c.radius.md}px` }}
>
Resubscribe
</Button>
@@ -299,7 +299,7 @@ const OpenSwarmProCard: React.FC = () => {
disabled={managing}
size="small"
variant={isCanceled ? 'outlined' : 'contained'}
sx={{ textTransform: 'none', fontSize: '0.78rem', borderRadius: `${c.radius.md}px` }}
sx={{ textTransform: 'none', fontSize: c.font.size.xs, borderRadius: `${c.radius.md}px` }}
>
{managing ? 'Opening…' : 'Manage in Stripe'}
</Button>
@@ -37,15 +37,15 @@ const SubscriptionCard: React.FC<{ provider: SubscriptionProvider; connected: bo
} : {}),
}} />
<Box sx={{ minWidth: 0 }}>
<Typography sx={{ fontSize: '0.78rem', fontWeight: 600, color: c.text.primary }}>{provider.name}</Typography>
<Typography noWrap sx={{ fontSize: '0.65rem', color: connecting ? c.accent.primary : c.text.muted, transition: 'color 0.2s ease' }}>
<Typography sx={{ fontSize: c.font.size.xs, fontWeight: 600, color: c.text.primary }}>{provider.name}</Typography>
<Typography noWrap sx={{ fontSize: c.font.size.micro, color: connecting ? c.accent.primary : c.text.muted, transition: 'color 0.2s ease' }}>
{connecting ? 'Waiting for authorization...' : provider.desc}
</Typography>
</Box>
</Box>
{isPreview ? (
<Typography sx={{ fontSize: '0.65rem', color: c.text.ghost, fontStyle: 'italic', flexShrink: 0 }}>
<Typography sx={{ fontSize: c.font.size.micro, color: c.text.ghost, fontStyle: 'italic', flexShrink: 0 }}>
Coming soon
</Typography>
) : connected ? (
@@ -53,26 +53,26 @@ const SubscriptionCard: React.FC<{ provider: SubscriptionProvider; connected: bo
<CircularProgress size={14} sx={{ color: c.text.ghost }} />
) : (
<Box sx={{ position: 'relative', flexShrink: 0, minWidth: 72, height: 16 }}>
<Typography className="sub-rest" sx={{ position: 'absolute', right: 0, top: 0, fontSize: '0.68rem', fontWeight: 500, color: c.status.success, transition: 'opacity 0.18s ease' }}>
<Typography className="sub-rest" sx={{ position: 'absolute', right: 0, top: 0, fontSize: c.font.size.xxs, fontWeight: 500, color: c.status.success, transition: 'opacity 0.18s ease' }}>
Connected
</Typography>
<Typography className="sub-undo" onClick={onDisconnect} sx={{ position: 'absolute', right: 0, top: 0, fontSize: '0.68rem', color: c.text.tertiary, cursor: 'pointer', opacity: 0, pointerEvents: 'none', transition: 'opacity 0.18s ease', '&:hover': { color: c.status.error } }}>
<Typography className="sub-undo" onClick={onDisconnect} sx={{ position: 'absolute', right: 0, top: 0, fontSize: c.font.size.xxs, color: c.text.tertiary, cursor: 'pointer', opacity: 0, pointerEvents: 'none', transition: 'opacity 0.18s ease', '&:hover': { color: c.status.error } }}>
Disconnect
</Typography>
</Box>
)
) : connecting && userCode ? (
<Box sx={{ textAlign: 'right', flexShrink: 0 }}>
<Typography sx={{ fontSize: '0.68rem', color: c.text.muted }}>Enter code:</Typography>
<Typography sx={{ fontSize: '0.85rem', fontWeight: 700, color: c.accent.primary, fontFamily: c.font.mono, letterSpacing: '0.1em' }}>{userCode}</Typography>
<Typography sx={{ fontSize: c.font.size.xxs, color: c.text.muted }}>Enter code:</Typography>
<Typography sx={{ fontSize: c.font.size.base, fontWeight: 700, color: c.accent.primary, fontFamily: c.font.mono, letterSpacing: '0.1em' }}>{userCode}</Typography>
</Box>
) : connecting ? (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.8, flexShrink: 0 }}>
<CircularProgress size={14} sx={{ color: c.accent.primary }} />
<Typography sx={{ fontSize: '0.68rem', color: c.accent.primary }}>Connecting...</Typography>
<Typography sx={{ fontSize: c.font.size.xxs, color: c.accent.primary }}>Connecting...</Typography>
</Box>
) : (
<Button onClick={onConnect} variant="outlined" size="small" sx={{ textTransform: 'none', fontSize: '0.7rem', fontWeight: 600, color: c.text.primary, borderColor: c.border.medium, borderRadius: `${c.radius.sm}px`, minWidth: 72, flexShrink: 0, '&:hover': { borderColor: c.accent.primary, bgcolor: `${c.accent.primary}0a` }, transition: 'all 0.2s ease' }}>
<Button onClick={onConnect} variant="outlined" size="small" sx={{ textTransform: 'none', fontSize: c.font.size.xxs, fontWeight: 600, color: c.text.primary, borderColor: c.border.medium, borderRadius: `${c.radius.sm}px`, minWidth: 72, flexShrink: 0, '&:hover': { borderColor: c.accent.primary, bgcolor: `${c.accent.primary}0a` }, transition: 'all 0.2s ease' }}>
Connect
</Button>
)}
@@ -169,10 +169,10 @@ const SubscriptionCards: React.FC = () => {
return (
<Box sx={{ p: 2, borderRadius: `${c.radius.md}px`, border: `1px solid ${c.border.subtle}`, textAlign: 'center' }}>
<CircularProgress size={18} sx={{ color: c.text.ghost, mb: 1 }} />
<Typography sx={{ fontSize: '0.78rem', color: c.text.muted, mb: 0.5 }}>
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.muted, mb: 0.5 }}>
Starting subscription service...
</Typography>
<Typography sx={{ fontSize: '0.65rem', color: c.text.ghost }}>
<Typography sx={{ fontSize: c.font.size.micro, color: c.text.ghost }}>
This connects your existing AI subscriptions. If this doesn't load, make sure Node.js is installed.
</Typography>
</Box>
@@ -187,14 +187,14 @@ const SubscriptionCards: React.FC = () => {
borderRadius: `${c.radius.md}px`, border: `1px solid ${c.border.subtle}`,
bgcolor: c.bg.surface,
}}>
<Typography sx={{ fontSize: '0.72rem', color: c.text.secondary, flex: 1, lineHeight: 1.4 }}>
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.secondary, flex: 1, lineHeight: 1.4 }}>
{connectError}
</Typography>
<Box
role="button"
aria-label="Dismiss"
onClick={() => setConnectError(null)}
sx={{ color: c.text.muted, cursor: 'pointer', fontSize: '0.9rem', lineHeight: 1, px: 0.3, '&:hover': { color: c.text.secondary } }}
sx={{ color: c.text.muted, cursor: 'pointer', fontSize: c.font.size.base, lineHeight: 1, px: 0.3, '&:hover': { color: c.text.secondary } }}
>
×
</Box>
@@ -94,9 +94,9 @@ const UsageStats: React.FC = () => {
bgcolor: c.bg.elevated,
border: `1px solid ${c.border.subtle}`,
};
const labelSx = { fontSize: '0.58rem', fontWeight: 700, color: c.text.ghost, textTransform: 'uppercase' as const, letterSpacing: '0.06em', mb: 0.25 };
const valueSx = { fontSize: '1.05rem', fontWeight: 700, color: c.text.primary, lineHeight: 1.2 };
const subSx = { fontSize: '0.62rem', color: c.text.tertiary, mt: 0.25 };
const labelSx = { fontSize: c.font.size.micro, fontWeight: 700, color: c.text.ghost, textTransform: 'uppercase' as const, letterSpacing: '0.06em', mb: 0.25 };
const valueSx = { fontSize: c.font.size.md, fontWeight: 700, color: c.text.primary, lineHeight: 1.2 };
const subSx = { fontSize: c.font.size.micro, color: c.text.tertiary, mt: 0.25 };
const modelEntries = Object.entries(stats.models_used || {}).sort((a: any, b: any) => b[1] - a[1]) as [string, number][];
const providerEntries = Object.entries(stats.providers_used || {}).sort((a: any, b: any) => b[1] - a[1]) as [string, number][];
@@ -140,8 +140,8 @@ const UsageStats: React.FC = () => {
bgcolor: `${c.accent.primary}0F`, border: `1px solid ${c.accent.primary}26`,
display: 'flex', alignItems: 'center', gap: 1,
}}>
<Typography sx={{ fontSize: '0.95rem', lineHeight: 1 }}></Typography>
<Typography sx={{ fontSize: '0.74rem', color: c.text.secondary, fontStyle: 'italic', lineHeight: 1.4 }}>
<Typography sx={{ fontSize: c.font.size.md, lineHeight: 1 }}></Typography>
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.secondary, fontStyle: 'italic', lineHeight: 1.4 }}>
{savingsQuip}
</Typography>
</Box>
@@ -220,15 +220,15 @@ const UsageStats: React.FC = () => {
return (
<Box key={model} sx={{ mb: 1 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', mb: 0 }}>
<Typography sx={{ fontSize: '0.78rem', color: c.text.muted, fontWeight: 500 }}>{model}</Typography>
<Typography sx={{ fontSize: '0.68rem', color: c.text.tertiary, fontFamily: c.font.mono }}>
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.muted, fontWeight: 500 }}>{model}</Typography>
<Typography sx={{ fontSize: c.font.size.xxs, color: c.text.tertiary, fontFamily: c.font.mono }}>
{count} ({pct}%)
</Typography>
</Box>
<PixelBar value={count} max={stats.total_sessions} palette={PIXEL_BLUE} />
</Box>
);
}) : <Typography sx={{ fontSize: '0.75rem', color: c.text.ghost }}>No sessions yet</Typography>}
}) : <Typography sx={{ fontSize: c.font.size.xs, color: c.text.ghost }}>No sessions yet</Typography>}
</Box>
<Box sx={{ ...cardSx, p: 2 }}>
@@ -239,15 +239,15 @@ const UsageStats: React.FC = () => {
return (
<Box key={tool} sx={{ mb: 1 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', mb: 0 }}>
<Typography sx={{ fontSize: '0.72rem', color: c.text.muted, fontWeight: 500 }}>{shortName}</Typography>
<Typography sx={{ fontSize: '0.62rem', color: c.text.tertiary, fontFamily: c.font.mono }}>
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.muted, fontWeight: 500 }}>{shortName}</Typography>
<Typography sx={{ fontSize: c.font.size.micro, color: c.text.tertiary, fontFamily: c.font.mono }}>
{count} call{count !== 1 ? 's' : ''} ({pct}%)
</Typography>
</Box>
<PixelBar value={count} max={maxToolCount} />
</Box>
);
}) : <Typography sx={{ fontSize: '0.75rem', color: c.text.ghost }}>No tool calls yet</Typography>}
}) : <Typography sx={{ fontSize: c.font.size.xs, color: c.text.ghost }}>No tool calls yet</Typography>}
</Box>
</Box>
</Box>
+30 -1
View File
@@ -1,3 +1,30 @@
// The type scale. OpenSwarm had ~30 near-duplicate hardcoded rem sizes (0.78/0.8/0.82/0.85...), which
// reads as noise; snap to these 7 steps so type feels intentional and consistent. px comments assume a
// 16px root. Use c.font.size.* instead of a raw rem string in anything you touch.
export interface FontSizeScale {
micro: string; // 10px, densest chrome (badges, kbd hints)
xxs: string; // 11px, tiny meta / timestamps
xs: string; // 12px, captions, meta, micro labels
sm: string; // 13px, secondary / small body
base: string; // 14px, default body + most UI text
md: string; // 16px, emphasized body, inputs
lg: string; // 18px, subheads
xl: string; // 22px, section headings
display: string; // 28px, hero / empty-state headline
}
export const fontSize: FontSizeScale = {
micro: '0.625rem',
xxs: '0.6875rem',
xs: '0.75rem',
sm: '0.8125rem',
base: '0.875rem',
md: '1rem',
lg: '1.125rem',
xl: '1.375rem',
display: '1.75rem',
};
export interface ClaudeTokens {
bg: { page: string; surface: string; elevated: string; secondary: string; inverse: string };
text: { primary: string; secondary: string; tertiary: string; muted: string; inverse: string; ghost: string };
@@ -7,7 +34,7 @@ export interface ClaudeTokens {
radius: { xs: number; sm: number; md: number; lg: number; xl: number; full: number };
status: { success: string; successBg: string; warning: string; warningBg: string; error: string; errorBg: string; info: string; infoBg: string };
user: { bubble: string };
font: { sans: string; mono: string };
font: { sans: string; mono: string; size: FontSizeScale };
transition: string;
}
@@ -58,6 +85,7 @@ export const lightTokens: ClaudeTokens = {
font: {
sans: '"Anthropic Sans", ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
mono: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
size: fontSize,
},
transition: 'all 150ms cubic-bezier(0.165, 0.85, 0.45, 1)',
};
@@ -109,6 +137,7 @@ export const darkTokens: ClaudeTokens = {
font: {
sans: '"Anthropic Sans", ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
mono: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
size: fontSize,
},
transition: 'all 150ms cubic-bezier(0.165, 0.85, 0.45, 1)',
};