chore: audit all components #2
@@ -13,6 +13,14 @@ is the first downstream consumer; its screens drive the order.
|
||||
|
||||
Gaps surfaced by the signed comparison; ordered by signed's usage.
|
||||
|
||||
- [ ] **Platform metric fidelity** — native-sizing pass over the theme and
|
||||
components: stated `control_height` per platform (macOS 24 measured /
|
||||
Windows 32 / GNOME ≈34 / KDE ≈33), Linux root font sizes (GNOME 14.67px,
|
||||
KDE 13.33px), platform overlay/sm radii, per-source border + focus
|
||||
policy (macOS/GNOME borderless fills, Windows/KDE 1px), titlebar
|
||||
heights (GNOME 47), a theme spacing scale, switch/icon calibration;
|
||||
ranked findings in
|
||||
[platform-metrics-audit.md](platform-metrics-audit.md).
|
||||
- [ ] **Virtual list** — generic virtualized variable-height rows with a
|
||||
scroll handle, promoted from the editor views' pattern over gpui's
|
||||
`list`; signed's PR lists and commit diff drive it.
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
# Platform metrics audit — Apple HIG, GNOME HIG, Fluent, KDE HIG
|
||||
|
||||
Why our controls read small and foreign next to native ones. Assessed 2026-11
|
||||
against the primary sources: the libadwaita stylesheet (`libadwaita` `main`,
|
||||
`src/stylesheet/**.scss`), WinUI 3 theme resources (`microsoft-ui-xaml`
|
||||
CommonStyles) and the Learn design pages, Kirigami `units.cpp` + Breeze
|
||||
`breezemetrics.h`/`breezestyle.cpp`, and the Apple HIG pages. Apple publishes
|
||||
no pixel metrics, so macOS numbers are **measured on this machine via AppKit
|
||||
`fittingSize`** — note the OS-version variance called out below. Queue entry:
|
||||
[CHECKLIST.md](CHECKLIST.md) — "Platform metric fidelity".
|
||||
|
||||
## Verdict
|
||||
|
||||
Three real problems, none of them "spacing values are off by a bit":
|
||||
|
||||
1. **Heights.** `control_height` is derived as `1.75 × root_font_size`.
|
||||
Native platforms do not size controls that way — Windows fixes buttons at
|
||||
32px, GNOME and Breeze add fixed padding to the font height. Result: our
|
||||
buttons are −23% vs native on Windows, −18% on GNOME. The main "looks
|
||||
small" driver.
|
||||
2. **Radii.** Only `radius.control` adapts per platform; `sm`/`overlay` are
|
||||
frozen at 4/10. Native overlay radii span 5px (KDE) to 15px (GNOME) — we
|
||||
are 5px off in both directions.
|
||||
3. **Borders.** We paint `border_1()` on nearly everything (shadcn-style).
|
||||
That matches Windows and KDE and contradicts macOS and GNOME/libadwaita,
|
||||
whose controls are borderless fills at normal contrast. The "feels weird"
|
||||
driver.
|
||||
|
||||
Structural gap underneath the spacing complaint: **the theme has no spacing
|
||||
tokens** — components hardcode gpui utilities (`p_1`, `px_2`, `gap_1p5`,
|
||||
`min_w_56`), so platform rhythm (GNOME's 6px grid, KDE's 4/6/8) cannot be
|
||||
expressed.
|
||||
|
||||
## What the platforms specify
|
||||
|
||||
### Apple / macOS
|
||||
|
||||
The HIG publishes no pixel metrics for macOS — verified across the buttons,
|
||||
text-fields, toolbars, toggles, menus, windows, layout, sidebars, sheets,
|
||||
materials and designing-for-macos pages. What is published: the type ramp
|
||||
(body 13pt, min 10pt) and the 44×44pt hit-region rule. Everything else below
|
||||
was measured locally (AppKit, regular control size, this macOS build):
|
||||
|
||||
| Control | Measured | Confidence |
|
||||
|---|---|---|
|
||||
| Push button | **24pt** (small 20, mini 16) | measured |
|
||||
| Rounded text field | **24pt** | measured |
|
||||
| Pop-up button / segmented | **24pt** / **24pt** | measured |
|
||||
| Switch | **54×24** | measured |
|
||||
| Checkbox (empty control) | **16×16** | measured |
|
||||
| Slider control height | **16pt** | measured |
|
||||
| Window titlebar | **32pt** on this build; 28pt on older macOS | measured, version-dependent |
|
||||
| Body text | 13pt | published |
|
||||
|
||||
Border language: toolbar items and image buttons are *explicitly borderless*
|
||||
("toolbar items don't include a bezel"); hierarchy is expressed by fill, not
|
||||
borders; fields keep a hairline bezel; menus/popovers separate by shadow and
|
||||
material, not borders. Concentricity rule: "buttons, text fields, headers,
|
||||
and footers have corner radii that are concentric with bar corners" (rule
|
||||
only, no values).
|
||||
|
||||
### GNOME / libadwaita
|
||||
|
||||
Exact values from the stylesheet; GTK px are logical pixels at 96dpi.
|
||||
|
||||
| Metric | Value |
|
||||
|---|---|
|
||||
| Button total height | min-height 24 + padding 5px 10px ⇒ **≈34px** |
|
||||
| Entry | **34px** (border-box, no vertical padding) |
|
||||
| Header bar | **47px** |
|
||||
| Menu item | min-height **32px**, padding `0 12px`; container padding 6px |
|
||||
| Switch | knob 20px, trough ≈**46×26** |
|
||||
| Checkbox | 14px + 3px pad each side ⇒ **20px**, radius **6px** |
|
||||
| Radius ladder | button/entry **9px** (`$button_radius`), checkbox 6px, cards 12px, menu/popover/dialog/window **15px**, menu items 9px |
|
||||
| Font | Adwaita Sans 11pt = **14.67px** |
|
||||
| Focus | **2px accent outline** (accent @50% default, 80% high contrast) |
|
||||
|
||||
Borders: **controls are borderless at normal contrast** — raised buttons,
|
||||
entries, switches, menus draw shape from translucent `currentColor`-mix
|
||||
fills. 1px borders are a high-contrast affordance only. Always-on hairlines
|
||||
are reserved for the window outline, headerbar bottom shade, frames, and
|
||||
boxed-list row separators.
|
||||
|
||||
### Windows 11 / WinUI 3 (Fluent 2)
|
||||
|
||||
Published tokens from the theme-resource XAML and Learn pages, at 100%
|
||||
scaling:
|
||||
|
||||
| Metric | Value |
|
||||
|---|---|
|
||||
| Button | **32px** (padding 11,5,11,6 + 1px border + Body 14/20); `ButtonHeight=32` lives in OS generic.xaml |
|
||||
| TextBox | **32px** (`TextControlThemeMinHeight`, OS generic.xaml — not fetched) |
|
||||
| CheckBox box | **20×20**, 1px stroke (row 32) |
|
||||
| ToggleSwitch | track **40×20** pill; knob **12×12** resting → 14 hover |
|
||||
| Radius | ControlCornerRadius **4px**, OverlayCornerRadius **8px** |
|
||||
| Title bar | **32px** standard, 48px "Tall" |
|
||||
| Type ramp | Caption 12, Body **14**, Body Large 18, Subtitle 20, Title 28 |
|
||||
| Spacing | 8 between buttons, 12 control↔label, 16 edge↔text — a **4px grid** |
|
||||
| Focus | system ring drawn outside the control (negative focus margin) |
|
||||
|
||||
Borders: buttons and textboxes carry a **1px elevation border** (gradient
|
||||
stroke, lighter top + darker bottom); the accent button keeps a border too;
|
||||
checkbox box 1px; switch track 1px while off; slider borderless except the
|
||||
thumb.
|
||||
|
||||
### KDE Plasma 6 / Breeze
|
||||
|
||||
Source constants (`breezemetrics.h`, `units.cpp`); the HIG names the usage
|
||||
rules, not px.
|
||||
|
||||
| Metric | Value |
|
||||
|---|---|
|
||||
| Kirigami units | gridUnit **18**, smallSpacing **4**, mediumSpacing **6**, largeSpacing **8**, cornerRadius **5** |
|
||||
| Button | fontHeight + 2×(6+2) ⇒ **≈33px** at the default 10pt font |
|
||||
| Line edit | fontHeight + 12 ⇒ **≈29px** |
|
||||
| Checkbox | **20×20**, radius 4 |
|
||||
| Switch (qqc2) | **36×18**, knob 18 |
|
||||
| Radius | **5px** everywhere — buttons, line edits, menus, popups, windows |
|
||||
| Font | Noto Sans 10pt = **13.33px** |
|
||||
| Titlebar (KWin) | max(fontHeight, 2×gridUnit = 36) + margins ⇒ **≈44px** |
|
||||
| Focus | rounded ring filled highlight @30%; item views get a focus underline |
|
||||
|
||||
Borders: 1px outline (`PenWidth::Frame`) on all controls; **flat buttons are
|
||||
borderless until hover/press/check**; menus and popups framed 1px.
|
||||
|
||||
## What our tokens resolve to today
|
||||
|
||||
`SizeTable::with_root_font_size` medium class (height 1.75rem, padding
|
||||
1rem) fed by each adapter's inputs in `crates/theme/src/source/`:
|
||||
|
||||
| | macOS (root 13) | Windows (root 14) | GNOME (fallback 16) | KDE (fallback 16) |
|
||||
|---|---|---|---|---|
|
||||
| control_height | 22.75 | 24.5 | 28 | 28 |
|
||||
| input_height | 24 (stated) | 32 (stated) | 30 | 30 |
|
||||
| padding_x | 13 | 14 | 16 | 16 |
|
||||
| text / icon / indicator | 13 / 13 / 15.9 | 14 / 14 / 17.2 | 16 / 16 / 19.6 | 16 / 16 / 19.6 |
|
||||
| radius sm / control / overlay | 4 / 6 (stated) / 10 | 4 / 4 (stated) / 10 | 4 / 6 / 10 | 4 / 6 / 10 |
|
||||
| titlebar | max(22.75, 34 floor) = 34 | 32 (stated) | 34 | 34 |
|
||||
| switch (1.65×h × h, knob 1.225rem) | 37.5×22.75 | 40.4×24.5 | 46.2×28 | 46.2×28 |
|
||||
|
||||
## Gap analysis
|
||||
|
||||
### Heights — the "looks small" bug
|
||||
|
||||
| Button height | Native | Ours | Delta |
|
||||
|---|---|---|---|
|
||||
| Windows | 32 | 24.5 | **−7.5px (−23%)** |
|
||||
| GNOME | ≈34 | 28 | **−6px (−18%)** |
|
||||
| KDE | ≈33 | 28 | −5px |
|
||||
| macOS | 24 | 22.75 | −1.25pt |
|
||||
|
||||
Root cause: native heights are not `k × font`. Windows fixes 32px; GNOME is
|
||||
font + 10px padding; Breeze is fontHeight + 16px. One rem factor cannot hit
|
||||
any of them — and the trap is that reading the real Linux fonts (14.67 /
|
||||
13.33px) without changing the model makes our buttons *smaller* (24.4 /
|
||||
23.3). The codebase already has the right seam: `SystemTheme.input_height`,
|
||||
an optional platform-stated value with ratio scaling for the other classes.
|
||||
`control_height` needs the same treatment. Our stated `input_height` values
|
||||
(macOS 24, Windows 32) are already exactly right — the derived button height
|
||||
next to them is what is off.
|
||||
|
||||
Text compounds it on Linux: the GNOME/KDE adapters never state
|
||||
`root_font_size`, so the 16px fallback makes labels oversized (16 vs 14.67 /
|
||||
13.33) while controls read undersized.
|
||||
|
||||
### Radii
|
||||
|
||||
| Radius | macOS | Windows | GNOME | KDE |
|
||||
|---|---|---|---|---|
|
||||
| control | 6 ~ bezel ≈6 | 4 ✓ | **6 vs 9** | 6 vs 5 ~ |
|
||||
| overlay (menu/popover/dialog) | 10 ~ (unpublished, ≈10–12) | **10 vs 8** | **10 vs 15** | **10 vs 5** |
|
||||
| sm (checkbox) | 4 ~ | 4 ~ | 4 vs 6 | 4 ✓ |
|
||||
|
||||
Only `control` is platform-overridable (`resolve`); `Radius::default`
|
||||
freezes sm/overlay/full. GNOME's whole ladder sits 3–5px above ours; KDE's
|
||||
overlays sit 5px below.
|
||||
|
||||
### Borders — the "feels weird" bug
|
||||
|
||||
Our current policy: `border_1()` on secondary button, input, select,
|
||||
checkbox, radio, menu, popover, dialog, combobox popup, command palette,
|
||||
toast, tooltip, alert, even slider/switch knobs. Primary button is
|
||||
borderless.
|
||||
|
||||
| Element | macOS | GNOME | Windows | KDE | Ours |
|
||||
|---|---|---|---|---|---|
|
||||
| secondary button | fill, **no border** | fill, **no border** | 1px elevation border | 1px border | 1px — ✗mac/GNOME |
|
||||
| primary button | fill, no border | fill, no border | **accent fill + border** | accent fill + border | no border — ✗Win/KDE |
|
||||
| input | hairline bezel | **borderless**, 2px accent outline on focus | 1px border → accent on focus | 1px border → focus color | 1px always |
|
||||
| menu / popover | **no border** (shadow/material) | no border (shadow + 1px black@5% ring) | framed flyout | 1px frame | 1px |
|
||||
| separator roles | hairline ✓ | hairline ✓ | ✓ | ✓ | ✓ |
|
||||
|
||||
Half-right on Windows/KDE, systematically wrong on macOS/GNOME — and even on
|
||||
Windows/KDE our primary button drops a border the natives keep. Our
|
||||
high-contrast mode sharpening borders toward the foreground is exactly
|
||||
GNOME's model (borders as a high-contrast affordance); it just never
|
||||
switches off at normal contrast.
|
||||
|
||||
### Spacing — structural, not numeric
|
||||
|
||||
No spacing tokens exist in `Theme`; components hardcode a fixed 4px grid:
|
||||
menu `p_1` + items `px_2 py_1`, popover `mt_1 min_w_32`, menu `min_w_56`,
|
||||
toast/tooltip `px_3`/`px_2 py_1`, button `gap_1p5`, hardcoded
|
||||
`max_w(480)`/`max_w(360)`. Native rhythms: GNOME = 6px base (menu padding
|
||||
12px, item min-height 32px — our GNOME menu rows ≈24px), KDE = 4/6/8,
|
||||
Windows and macOS ≈ 4px grid (we happen to match those two). Menu/popover
|
||||
density is frozen at a macOS-ish tuning.
|
||||
|
||||
### Per-component calibration
|
||||
|
||||
- **Switch**: macOS native 54×24 (ours 37.5 wide — too narrow); Windows
|
||||
40×20 with a 12px knob (ours 24.5 tall, 17px knob — too big); KDE 36×18
|
||||
(ours 46×28); only GNOME ≈ matches, by accident.
|
||||
- **Checkbox**: Windows/GNOME/KDE ≈20 (ours 17.2–19.6); macOS 16 ✓.
|
||||
- **Icons**: macOS ≈16 (ours 13), Windows 16 (ours 14); GNOME/KDE 16 ✓.
|
||||
- **Titlebar**: measured 32 on this macOS build vs our 34 floor (fine-ish;
|
||||
the floor silently drifted past older macOS's 28); GNOME headerbar 47 vs
|
||||
34 — a visible −13px; KDE ≈44 vs 34; Windows 32 stated ✓.
|
||||
|
||||
## What is already right
|
||||
|
||||
- The `input_height` stated-value seam — the pattern to generalize.
|
||||
- Windows `ControlCornerRadius` 4; stated titlebar 32.
|
||||
- Body text on macOS (13) and Windows (14); scrollbar autohide mapping;
|
||||
per-platform elevation stacks; high-contrast border sharpening mirrors
|
||||
GNOME's model.
|
||||
- GNOME accent via portal/gsettings, KDE `DecorationFocus`/`DecorationHover`
|
||||
role mapping.
|
||||
|
||||
## Recommended changes, in priority order
|
||||
|
||||
1. **State `control_height` per platform** — add `control_height:
|
||||
Option<Pixels>` beside `input_height` on `SystemTheme`, reuse the
|
||||
ratio-scaling pattern: macOS 24 (measured), Windows 32, GNOME ≈34, KDE
|
||||
≈33. Fixes the smallness everywhere in one change.
|
||||
2. **State `root_font_size` from Linux font settings** — GNOME
|
||||
`font-name` (11pt ⇒ 14.67px), KDE `kdeglobals [General] font=` (10pt ⇒
|
||||
13.33px); the parsing seams already exist for colors.
|
||||
3. **Make the radius ladder platform-overridable** — stated overlay/sm
|
||||
radius on `SystemTheme`: GNOME 15/6, Windows 8/4, KDE 5/4, macOS ≈10/4;
|
||||
keep preset > system precedence.
|
||||
4. **Per-source border and focus policy** — a theme-level token consumed by
|
||||
components: secondary button and inputs drop borders on macOS/GNOME
|
||||
(fill tints + 2px accent focus outline), keep 1px on Windows/KDE;
|
||||
primary gains a border on Windows/KDE; menus/popovers shadow-only on
|
||||
macOS/GNOME.
|
||||
5. **State `titlebar_height` per platform** — GNOME 47, KDE ≈44; macOS
|
||||
queried rather than a drifted floor (32 on current builds, 28 older);
|
||||
Windows stays stated.
|
||||
6. **Add a minimal spacing scale to `Theme`** driven by a per-platform base
|
||||
unit (GNOME 6, KDE 4/6/8, others 4); route menu/popover paddings, menu
|
||||
item min-height (GNOME 32), and the hardcoded `min_w_`/`max_w` values
|
||||
through it.
|
||||
7. **Calibrate switches and icons per platform** — macOS 54×24, Windows
|
||||
40×20 with 12px knob, KDE 36×18; icon 16 on macOS/Windows.
|
||||
|
||||
## Source confidence notes
|
||||
|
||||
Everything in the GNOME/Windows/KDE tables is quoted from the cited
|
||||
stylesheets, sources, or Learn pages (published). KDE button/line-edit/title
|
||||
heights are formulas from source constants (derived). Windows TextBox height
|
||||
token and the focus-stroke pixel spec live in the OS `generic.xaml` and were
|
||||
not retrievable — low confidence, commonly cited as 32. Apple publishes no
|
||||
macOS control metrics; all macOS control numbers here are AppKit
|
||||
measurements from 2026-11 on this machine and may shift across macOS
|
||||
releases (titlebar already differs from older builds).
|
||||
Reference in New Issue
Block a user