Normalize frontend typography + enable font smoothing (#1546)

Lighter, more consistent type across the dashboard and agent UI:
unify title sizes, drop semibold/bold in chrome to medium, harmonize
stray text-sm to text-xs, normalize chat markdown headings, and enable
antialiased font smoothing (fixes heavy text on dark backgrounds).

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
Johannes du Plessis 2026-06-16 14:56:11 -07:00 • committed by GitHub
parent 801f93b4de
commit 876836bdfe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 58 additions and 52 deletions

View file

@ -38,7 +38,7 @@ export function AppShell({
</Link>
)}
<header className="mb-8">
<h1 className="font-heading text-lg font-medium">{title}</h1>
<h1 className="font-heading text-base font-medium">{title}</h1>
{description && (
<p className="mt-1 text-xs text-muted-foreground">{description}</p>
)}

View file

@ -98,11 +98,11 @@ export function AgentThreadView({ thread }: AgentThreadViewProps) {
</div>
) : isHydrating ? (
<div className="flex flex-1 flex-col items-center justify-center gap-4 px-6">
<p className="text-sm text-[var(--ui-text-dim)]">Loading conversation…</p>
<p className="text-xs text-[var(--ui-text-dim)]">Loading conversation…</p>
</div>
) : (
<div className="flex flex-1 flex-col items-center justify-center gap-4 px-6">
<p className="text-sm text-[var(--ui-text-dim)]">
<p className="text-xs text-[var(--ui-text-dim)]">
This thread has no messages yet.
</p>
<div className="w-full max-w-3xl">

View file

@ -227,7 +227,7 @@ function ThreadGroup({
<button
type="button"
onClick={() => setCollapsed((value) => !value)}
className="flex w-full items-center gap-1 px-2 py-1 text-left text-[10px] font-semibold tracking-wide text-[var(--ui-text-dim)] uppercase transition-colors hover:text-[var(--ui-text-muted)]"
className="flex w-full items-center gap-1 px-2 py-1 text-left text-[10px] font-medium tracking-wide text-[var(--ui-text-dim)] uppercase transition-colors hover:text-[var(--ui-text-muted)]"
aria-expanded={!collapsed}
>
<ToggleIcon className="size-3" />
@ -270,7 +270,7 @@ function ResolvedThreadGroup({
<button
type="button"
onClick={() => setCollapsed((value) => !value)}
className="flex w-full items-center gap-1 px-2 py-1 text-left text-[10px] font-semibold tracking-wide text-[var(--ui-text-dim)] uppercase transition-colors hover:text-[var(--ui-text-muted)]"
className="flex w-full items-center gap-1 px-2 py-1 text-left text-[10px] font-medium tracking-wide text-[var(--ui-text-dim)] uppercase transition-colors hover:text-[var(--ui-text-muted)]"
aria-expanded={!collapsed}
>
<ToggleIcon className="size-3" />
@ -486,10 +486,10 @@ function ThreadRow({
<Dialog.Backdrop className="fixed inset-0 z-50 bg-black/50 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0" />
<Dialog.Popup className="fixed top-1/2 left-1/2 z-50 w-[min(28rem,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2 rounded-lg bg-popover p-6 text-popover-foreground shadow-md ring-1 ring-foreground/10 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95">
<div className="flex flex-col gap-4">
<Dialog.Title className="text-base font-semibold">
<Dialog.Title className="text-sm font-medium">
Delete thread
</Dialog.Title>
<Dialog.Description className="text-sm text-muted-foreground">
<Dialog.Description className="text-xs text-muted-foreground">
Delete "{thread.title}"? This cannot be undone.
</Dialog.Description>
<div className="mt-2 flex justify-end gap-2">

View file

@ -98,7 +98,7 @@ export function AgentsThreadsPage({
<main className="flex min-w-0 flex-1 flex-col overflow-y-auto px-6 py-8">
<div className="mx-auto flex w-full max-w-3xl flex-1 flex-col gap-6">
<div>
<h1 className="font-heading text-lg font-medium text-[var(--ui-text)]">
<h1 className="font-heading text-base font-medium text-[var(--ui-text)]">
Threads
</h1>
<p className="text-xs text-[var(--ui-text-muted)]">

View file

@ -139,7 +139,7 @@ export function AutomationEditor({ mode, schedule }: AutomationEditorProps) {
return (
<div className="flex min-w-0 flex-1 flex-col overflow-y-auto">
<header className="flex items-center justify-between gap-3 px-6 py-4 max-md:pt-14">
<div className="flex min-w-0 items-center gap-1.5 text-sm text-[var(--ui-text-dim)]">
<div className="flex min-w-0 items-center gap-1.5 text-xs text-[var(--ui-text-dim)]">
<Link
to="/agents/automations"
className="shrink-0 transition-colors hover:text-[var(--ui-text)]"
@ -179,10 +179,10 @@ export function AutomationEditor({ mode, schedule }: AutomationEditorProps) {
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Untitled automation"
className="w-full bg-transparent text-2xl font-semibold text-[var(--ui-text)] outline-none placeholder:text-[var(--ui-text-dim)]"
className="w-full bg-transparent text-base font-medium text-[var(--ui-text)] outline-none placeholder:text-[var(--ui-text-dim)]"
/>
<div className="mt-3 flex items-center gap-3 text-sm">
<div className="mt-3 flex items-center gap-3 text-xs">
<div className="flex items-center gap-2">
<Switch checked={enabled} onCheckedChange={setEnabled} />
<span className="text-[var(--ui-text-muted)]">
@ -255,7 +255,7 @@ export function AutomationEditor({ mode, schedule }: AutomationEditorProps) {
</div>
{errorMessage && (
<p className="mt-4 text-sm text-[var(--ui-danger)]">{errorMessage}</p>
<p className="mt-4 text-xs text-[var(--ui-danger)]">{errorMessage}</p>
)}
</div>
</div>
@ -264,7 +264,7 @@ export function AutomationEditor({ mode, schedule }: AutomationEditorProps) {
function SectionLabel({ children }: { children: React.ReactNode }) {
return (
<h2 className="mt-8 mb-2 text-sm font-medium text-[var(--ui-text-muted)]">
<h2 className="mt-8 mb-2 text-xs font-medium text-[var(--ui-text-muted)]">
{children}
</h2>
)
@ -310,7 +310,7 @@ function ModelPicker({
type="button"
disabled={disabled}
onClick={() => setOpen((value) => !value)}
className="flex items-center gap-1 text-sm text-[var(--ui-text-muted)] transition-opacity hover:opacity-80 disabled:opacity-60"
className="flex items-center gap-1 text-xs text-[var(--ui-text-muted)] transition-opacity hover:opacity-80 disabled:opacity-60"
>
<span>{formatModelSelection(models, selection)}</span>
{!disabled && <CaretDownIcon className="size-3.5 opacity-60" />}
@ -331,7 +331,7 @@ function ModelPicker({
setOpen(false)
}}
className={cn(
"flex w-full items-center gap-2 px-3 py-1.5 text-left text-sm whitespace-nowrap transition-colors hover:bg-[var(--ui-panel-2)]",
"flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs whitespace-nowrap transition-colors hover:bg-[var(--ui-panel-2)]",
selected
? "text-[var(--ui-text)]"
: "text-[var(--ui-text-muted)]"

View file

@ -42,10 +42,10 @@ export function AutomationsList() {
return (
<div className="flex min-w-0 flex-1 flex-col overflow-y-auto">
<div className="mx-auto w-full max-w-4xl px-6 py-8 max-md:pt-16">
<h1 className="text-2xl font-semibold text-[var(--ui-text)]">
<h1 className="text-base font-medium text-[var(--ui-text)]">
Automations
</h1>
<p className="mt-1 text-sm text-[var(--ui-text-muted)]">
<p className="mt-1 text-xs text-[var(--ui-text-muted)]">
Run Open SWE on a recurring schedule. Each run starts a fresh agent
thread.
</p>
@ -58,7 +58,7 @@ export function AutomationsList() {
</div>
<div className="mt-8 flex items-center justify-between">
<span className="text-sm font-medium text-[var(--ui-text-muted)]">
<span className="text-xs font-medium text-[var(--ui-text-muted)]">
{total} {total === 1 ? "automation" : "automations"}
</span>
<Link to="/agents/automations/new" className={buttonVariants()}>
@ -102,7 +102,7 @@ function StatCard({
<div className="text-xs text-[var(--ui-text-dim)]">{label}</div>
<div
className={cn(
"mt-1 text-2xl font-semibold",
"mt-1 text-lg font-medium",
highlight ? "text-[var(--ui-danger)]" : "text-[var(--ui-text)]"
)}
>
@ -121,7 +121,7 @@ function EmptyState() {
<h3 className="mt-4 text-sm font-medium text-[var(--ui-text)]">
No automations yet
</h3>
<p className="mt-1 max-w-sm text-sm text-[var(--ui-text-muted)]">
<p className="mt-1 max-w-sm text-xs text-[var(--ui-text-muted)]">
Schedule Open SWE to run on a recurring cadence — review code, triage
issues, or keep docs up to date.
</p>

View file

@ -100,16 +100,16 @@ export function OnboardingDialog() {
<Dialog.Popup className="fixed top-1/2 left-1/2 z-50 w-[min(28rem,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2 rounded-lg bg-popover p-6 text-popover-foreground shadow-md ring-1 ring-foreground/10 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95">
{step === "model" ? (
<div className="flex flex-col gap-4">
<Dialog.Title className="text-base font-semibold">
<Dialog.Title className="text-sm font-medium">
Choose your default model
</Dialog.Title>
<Dialog.Description className="text-sm text-muted-foreground">
<Dialog.Description className="text-xs text-muted-foreground">
Pick the model Open SWE uses when you don't specify one. You can
change this anytime in your settings.
</Dialog.Description>
<div className="flex flex-col gap-3">
<div className="flex items-center justify-between gap-3">
<span className="text-sm font-medium">Default model</span>
<span className="text-xs font-medium">Default model</span>
<Select
value={modelId}
onValueChange={(v) => v && setModelId(v)}
@ -127,7 +127,7 @@ export function OnboardingDialog() {
</Select>
</div>
<div className="flex items-center justify-between gap-3">
<span className="text-sm font-medium">Reasoning effort</span>
<span className="text-xs font-medium">Reasoning effort</span>
<Select
value={effort}
onValueChange={(v) => v && setEffort(v)}
@ -171,11 +171,11 @@ export function OnboardingDialog() {
<div className="flex flex-col gap-4">
<div className="flex items-center gap-3">
<IoLogoSlack className="size-6 shrink-0 text-muted-foreground" />
<Dialog.Title className="text-base font-semibold">
<Dialog.Title className="text-sm font-medium">
Connect your Slack account
</Dialog.Title>
</div>
<Dialog.Description className="text-sm text-muted-foreground">
<Dialog.Description className="text-xs text-muted-foreground">
Connect Slack so that when you tag Open SWE, it can resolve your
GitHub account. We use the email Slack verifies, which also lets
Linear mentions resolve to you.

View file

@ -226,7 +226,7 @@ function useConversations(key: string) {
function EmptyState({ onPick }: { onPick: (prompt: string) => void }) {
return (
<div className="flex flex-1 flex-col gap-4 p-4">
<p className="text-sm text-foreground">
<p className="text-[13px] text-foreground">
I've reviewed this PR. Ask me about the diff, the findings, or the
surrounding code — I have read-only access to the repository.
</p>
@ -239,7 +239,7 @@ function EmptyState({ onPick }: { onPick: (prompt: string) => void }) {
key={prompt}
type="button"
onClick={() => onPick(prompt)}
className="flex items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm text-foreground hover:bg-muted/60"
className="flex items-center gap-2 rounded-md px-2 py-1.5 text-left text-[13px] text-foreground hover:bg-muted/60"
>
<SparkleIcon className="size-4 shrink-0 text-muted-foreground" />
{prompt}
@ -327,7 +327,7 @@ function ChatBody({
>
<div
className={cn(
"text-sm text-foreground",
"text-[13px] text-foreground",
isUser
? "max-w-[85%] rounded-lg bg-muted px-3 py-2"
: "w-full",
@ -346,7 +346,7 @@ function ChatBody({
})}
{busy && (
<div className="flex justify-start">
<div className="px-3 py-2 text-sm text-muted-foreground">
<div className="px-3 py-2 text-xs text-muted-foreground">
Thinking…
</div>
</div>

View file

@ -93,7 +93,7 @@ export function ReviewSidebarPanel({ data }: { data: ReviewSidebarData }) {
return (
<div className="flex min-h-0 flex-1 flex-col pb-2">
<div className="flex items-center justify-between gap-2 px-4 py-1">
<span className="text-[10px] font-semibold tracking-wide text-[var(--ui-text-dim)] uppercase">
<span className="text-[10px] font-medium tracking-wide text-[var(--ui-text-dim)] uppercase">
{data.title}
</span>
{hasGroups && (

View file

@ -41,7 +41,7 @@ export function ScheduleTriggerPicker({
<button
type="button"
onClick={() => setOpen((value) => !value)}
className="flex w-full items-center gap-2 rounded-lg px-3 py-2.5 text-sm text-[var(--ui-text-muted)] transition-colors hover:bg-[var(--ui-panel-2)] hover:text-[var(--ui-text)]"
className="flex w-full items-center gap-2 rounded-lg px-3 py-2.5 text-xs text-[var(--ui-text-muted)] transition-colors hover:bg-[var(--ui-panel-2)] hover:text-[var(--ui-text)]"
>
<PlusIcon className="size-4" />
{triggerLabel}
@ -49,7 +49,7 @@ export function ScheduleTriggerPicker({
{open && (
<div className="absolute top-full left-0 z-50 mt-1 w-72 overflow-hidden rounded-xl border border-[var(--ui-border)] bg-[var(--ui-surface)] py-1 shadow-lg">
<div className="flex items-center gap-2 px-3 py-1.5 text-[11px] font-semibold tracking-wide text-[var(--ui-text-dim)] uppercase">
<div className="flex items-center gap-2 px-3 py-1.5 text-[11px] font-medium tracking-wide text-[var(--ui-text-dim)] uppercase">
<ClockIcon className="size-3.5" />
Scheduled
</div>
@ -62,7 +62,7 @@ export function ScheduleTriggerPicker({
setOpen(false)
}}
className={cn(
"flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-[var(--ui-text-muted)] transition-colors hover:bg-[var(--ui-panel-2)] hover:text-[var(--ui-text)]"
"flex w-full items-center gap-2 px-3 py-2 text-left text-xs text-[var(--ui-text-muted)] transition-colors hover:bg-[var(--ui-panel-2)] hover:text-[var(--ui-text)]"
)}
>
<span className="flex-1">{option.label}</span>

View file

@ -185,7 +185,7 @@ export function AgentMessage({
{isExpanded && (
<div className="pt-1 pb-1 space-y-0.5">
{item.chunks.map((chunk, chunkIndex) => (
<div key={chunk.toolCallId || `explored-chunk-${item.id}-${chunkIndex}`} className="flex-1 min-w-0 text-gray-500">
<div key={chunk.toolCallId || `explored-chunk-${item.id}-${chunkIndex}`} className="flex-1 min-w-0 text-[color:var(--ui-text-dim)]">
<ToolExecution
chunk={chunk}
projectPath={projectPath}

View file

@ -23,7 +23,7 @@ export function ChunkRenderer({
return <span className="text-red-400">{chunk.text}</span>;
case "list":
return (
<div className="text-gray-300 ml-2">
<div className="text-[color:var(--ui-text-muted)] ml-2">
{chunk.lines.map((line, i) => (
<div key={i}>- {line}</div>
))}

View file

@ -345,7 +345,7 @@ export const CloudPromptBar = memo(function CloudPromptBarComponent({
>
{isDragOver && (
<div className="pointer-events-none absolute inset-0 z-20 flex items-center justify-center rounded-2xl bg-[var(--ui-surface)]/80 backdrop-blur-sm">
<span className="rounded-md bg-[var(--ui-panel-2)] px-3 py-1.5 text-sm font-medium text-[color:var(--ui-accent)]">
<span className="rounded-md bg-[var(--ui-panel-2)] px-3 py-1.5 text-xs font-medium text-[color:var(--ui-accent)]">
Drop images here
</span>
</div>

View file

@ -32,17 +32,17 @@ const STREAMDOWN_ANIMATED = {
const STREAMDOWN_COMPONENTS = {
h1: ({ children }: { children?: ReactNode }) => (
<div className="text-[color:var(--ui-accent)] text-[20px] font-semibold mt-4 mb-2 tracking-tight">
<div className="text-[color:var(--ui-text)] text-[15px] font-medium mt-4 mb-1.5 tracking-tight">
{children}
</div>
),
h2: ({ children }: { children?: ReactNode }) => (
<div className="text-[color:var(--ui-accent)] text-[17px] font-semibold mt-3 mb-2 tracking-tight">
<div className="text-[color:var(--ui-text)] text-[14px] font-medium mt-3 mb-1.5 tracking-tight">
{children}
</div>
),
h3: ({ children }: { children?: ReactNode }) => (
<div className="text-[color:var(--ui-accent)] text-[15px] font-semibold mt-3 mb-1">
<div className="text-[color:var(--ui-text)] text-[13px] font-medium mt-3 mb-1">
{children}
</div>
),

View file

@ -212,7 +212,7 @@ function UserMappingsSection({ enabled }: { enabled: boolean }) {
items.map((m: UserMapping) => (
<div
key={m.github_login}
className="flex items-center justify-between gap-2 border-b border-border py-1.5 text-sm last:border-b-0"
className="flex items-center justify-between gap-2 border-b border-border py-1.5 text-xs last:border-b-0"
>
<div className="flex min-w-0 flex-col">
<span className="truncate font-medium">{m.github_login}</span>

View file

@ -28,12 +28,12 @@ function EditAutomationPage() {
return (
<div className="mx-auto w-full max-w-3xl px-6 py-16 text-center">
<p className="text-sm text-[var(--ui-text-muted)]">
<p className="text-xs text-[var(--ui-text-muted)]">
This automation could not be found.
</p>
<Link
to="/agents/automations"
className="mt-3 inline-block text-sm text-[var(--ui-accent)] hover:underline"
className="mt-3 inline-block text-xs text-[var(--ui-accent)] hover:underline"
>
Back to Automations
</Link>

View file

@ -175,7 +175,10 @@ function ReviewDetailPage() {
<span className="inline-flex min-w-0 items-center gap-1.5 truncate">
<GitPullRequestIcon className="size-3.5 shrink-0 text-muted-foreground" />
<span className="truncate font-medium">
{owner}/{repo}#{number}
{owner}/{repo}
<span className="ml-1.5 font-normal text-muted-foreground">
#{number}
</span>
{detail.data ? ` ${detail.data.pr.title}` : ""}
</span>
</span>
@ -632,7 +635,7 @@ function PrHeader({ detail }: { detail: ReviewDetail }) {
<GitPullRequestIcon className="size-3" />
{pr.state}
</span>
<h1 className="mt-2 text-lg font-medium">
<h1 className="mt-2 text-base font-medium">
<a
href={detail.url}
target="_blank"

View file

@ -66,7 +66,7 @@ function ReviewsPage() {
return (
<main className="min-w-0 flex-1 overflow-y-auto">
<div className="mx-auto max-w-3xl px-6 py-8">
<h1 className="font-heading text-lg font-medium text-[var(--ui-text)]">
<h1 className="font-heading text-base font-medium text-[var(--ui-text)]">
PR Reviews
</h1>
<p className="mt-1 text-xs text-[var(--ui-text-muted)]">

View file

@ -98,7 +98,7 @@ function UsagePage() {
Failed to load usage data: {leaderboard.error.message}
</p>
) : !leaderboard.data?.rows.length ? (
<div className="p-6 text-center text-sm text-muted-foreground">
<div className="p-6 text-center text-xs text-muted-foreground">
No Open SWE Agent usage has been recorded for{" "}
{PERIOD_LABELS[activePeriod].toLowerCase()} yet.
</div>
@ -128,7 +128,7 @@ function UsagePage() {
) : leaderboard.data?.reviewer_stats ? (
<ReviewerStats stats={leaderboard.data.reviewer_stats} />
) : (
<div className="p-6 text-center text-sm text-muted-foreground">
<div className="p-6 text-center text-xs text-muted-foreground">
No reviewer stats have been recorded for{" "}
{PERIOD_LABELS[activePeriod].toLowerCase()} yet.
</div>
@ -147,7 +147,7 @@ function UsageTable({
}) {
return (
<div className="overflow-x-auto">
<table className="w-full min-w-[760px] text-sm">
<table className="w-full min-w-[760px] text-xs">
<thead className="border-b border-border text-xs text-muted-foreground">
<tr>
<th className="w-14 px-4 py-3 text-left font-normal">Rank</th>
@ -238,7 +238,7 @@ function ReviewerStats({ stats }: { stats: ReviewerStatsPayload }) {
{cards.map((card) => (
<div key={card.label} className="rounded-md border border-border p-3">
<div className="text-xs text-muted-foreground">{card.label}</div>
<div className="mt-1 text-xl font-medium tabular-nums">
<div className="mt-1 text-lg font-medium tabular-nums">
{formatNumber(card.value)}
</div>
<div className="mt-1 text-xs text-muted-foreground">
@ -277,7 +277,7 @@ function CounterList({
<div>
<h3 className="text-xs font-medium text-muted-foreground">{title}</h3>
{rows.length ? (
<ul className="mt-2 space-y-2 text-sm">
<ul className="mt-2 space-y-2 text-xs">
{rows.map((row) => (
<li
key={row.name}

View file

@ -127,6 +127,6 @@
@apply bg-background text-foreground;
}
html {
@apply font-sans;
@apply font-sans antialiased;
}
}

View file

@ -44,6 +44,9 @@
background: var(--ui-bg);
font-size: 13px;
line-height: 1.45;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
.agents-ui * {