Tooling · Catalogs: the audit reads active and code; avatar and container sizes

The audit now holds fg_subtle on active, a selected row's fill, and
each syntax color over the editor's surface, a code block and the
current line. What it found is fixed at the source: fg_subtle is
#696764 light and #94928f dark, syntax.comment follows it, and the
light number, constant and attribute darken to #a3591b, #aa5529 and
#9f5d12. The token viewer adds avatar and container sizes, draws a
container at its share of the widest so it stays in its row, and
names what it shows: the shared colors and scales. Tests pin every
variant, the hue each chart row reads and the code rows.
This commit is contained in:
ZacharyZhang-NY committed 2026-09-28 08:57:52 -04:00
1 parent d5271d03ea
commit a7e492f28e
7 files changed
+153 -27

No files matched your search

+1 -1
View File
@@ -224,4 +224,4 @@ Ely GPUI Component. A component library for GPUI, in light and dark.
- 2026-09-28: QRCodeScanner proven with `rqrr` 0.11: the host hands in frames as `RenderImage`s, read off the main thread; gpui 0.2.2 has no camera.
- 2026-09-28: WebView proven with `wry` 0.57: a child WKWebView over the window, placed from a canvas in prepaint at its box each frame and hidden once the box leaves the view. It draws above everything gpui paints and gpui's clipping does not reach it; gpui's test windows have no native handle, so the capture proves it.
- 2026-09-28: InspectorOverlay is gpui's inspector drawn by Ely, `tooling::install_inspector`, in debug builds only, as gpui has it.
- 2026-09-28: `fg_subtle` meets 4.5:1 on every surface it sits on, #6f6d6a light and #8c8a87 dark; the contrast audit found #82807d and #858380 under it.
- 2026-09-28: every text color meets 4.5:1 on the surfaces it sits on: `fg_subtle` is #696764 light and #94928f dark, `syntax.comment` with it, and the light syntax's number, constant and attribute are #a3591b, #aa5529 and #9f5d12; the contrast audit found them under.
+2 -2
View File
@@ -17,7 +17,7 @@ pub fn sections(window: &mut Window, cx: &mut App) -> Vec<AnyElement> {
vec![
section(
"DesignTokenViewer",
"Every token of the theme in the mode shown, each named as code reads it: the colors with their hex, the syntax, chart and terminal colors, the type scale, radii, control and icon sizes, elevations and the spacing steps.",
"The theme's shared colors and scales in the mode shown, each named as code reads it: every palette, syntax, chart and terminal color with its hex, and the type, radius, control, icon, avatar and container sizes, the elevations and the spacing steps.",
cx,
)
.child(div().max_w(px(840.0)).child(DesignTokenViewer::new()))
@@ -56,7 +56,7 @@ pub fn sections(window: &mut Window, cx: &mut App) -> Vec<AnyElement> {
.into_any_element(),
section(
"A11yChecker",
"The theme's contrast in the mode shown: each text color over the surfaces it sits on at 4.5:1, the focus ring and the chart hues beside the page at 3:1, failures first. Ely's palettes pass in both modes, with high contrast and without.",
"The theme's contrast in the mode shown: each text color over the surfaces it sits on and each code color over the editor's at 4.5:1, the focus ring and the chart hues beside the page at 3:1, failures first. Ely's palettes pass in both modes, with high contrast and without.",
cx,
)
.child(div().max_w(px(560.0)).child(A11yChecker::new()))
+7 -7
View File
@@ -185,7 +185,7 @@ impl Palette {
border_strong: c(0xcccac8),
fg: c(0x181613),
fg_muted: c(0x605d5a),
fg_subtle: c(0x6f6d6a),
fg_subtle: c(0x696764),
fg_disabled: c(0xacaaa8),
accent: c(0x181613),
accent_hover: c(0x302d2b),
@@ -221,14 +221,14 @@ impl Palette {
syntax: Syntax {
keyword: c(0x714ca6),
string: c(0x317a45),
number: c(0xb3621e),
comment: c(0x82807d),
number: c(0xa3591b),
comment: c(0x696764),
function: c(0x2863ab),
type_name: c(0x1e7777),
constant: c(0xb1582b),
constant: c(0xaa5529),
property: c(0xa14759),
tag: c(0xa14759),
attribute: c(0xa25f12),
attribute: c(0x9f5d12),
operator: c(0x605d5a),
punctuation: c(0x605d5a),
variable: c(0x181613),
@@ -256,7 +256,7 @@ impl Palette {
border_strong: c(0x413f3d),
fg: c(0xf3f1f0),
fg_muted: c(0xafadab),
fg_subtle: c(0x8c8a87),
fg_subtle: c(0x94928f),
fg_disabled: c(0x5c5a58),
accent: c(0xf3f1f0),
accent_hover: c(0xd9d7d5),
@@ -293,7 +293,7 @@ impl Palette {
keyword: c(0xbba3e8),
string: c(0x8ac596),
number: c(0xeaab78),
comment: c(0x858380),
comment: c(0x94928f),
function: c(0x85b6e9),
type_name: c(0x84c9c8),
constant: c(0xeda382),
+45 -6
View File
@@ -6,7 +6,7 @@ use gpui::{
use crate::{
data_display::{Badge, Tone},
devtools::{contrast, over},
theme::{ActiveTheme, HUE_NAMES, Palette, Radius, TextSize},
theme::{ActiveTheme, HUE_NAMES, Palette, Radius, Syntax, TextSize},
typography::{Ellipsis, literal, tabular},
};
@@ -27,7 +27,7 @@ const TEXT_PAIRS: &[(&str, &[&str])] = &[
),
(
"fg_subtle",
&["bg", "surface", "sunken", "overlay", "hover"],
&["bg", "surface", "sunken", "overlay", "hover", "active"],
),
("link", &["bg", "surface", "sunken"]),
("on_accent", &["accent", "accent_hover"]),
@@ -38,6 +38,9 @@ const TEXT_PAIRS: &[(&str, &[&str])] = &[
("info", &["bg", "info_subtle"]),
];
/// The surfaces code sits on: the editor, a code block, the editor's current line.
const CODE: [&str; 3] = ["surface", "sunken", "hover"];
/// A color, what it stands on, the contrast between them, and the contrast it needs.
#[derive(Clone, Debug)]
struct Check {
@@ -63,7 +66,7 @@ impl Check {
}
}
/// Every pair in `palette`: its text over its surfaces at 4.5:1, the focus ring and the chart hues beside the page at 3:1.
/// Every pair in `palette`: its text over its surfaces and its code colors over the code surfaces at 4.5:1, the focus ring and the chart hues beside the page at 3:1.
fn audit(palette: &Palette) -> Vec<Check> {
let check = |front: String, fg: Hsla, back: &'static str, need: f32| {
let bg = palette.token(back);
@@ -81,6 +84,16 @@ fn audit(palette: &Palette) -> Vec<Check> {
.iter()
.map(move |back| check(front.to_string(), palette.token(front), back, TEXT))
});
let code = Syntax::NAMES.iter().flat_map(|name| {
CODE.iter().map(move |back| {
check(
format!("syntax.{name}"),
palette.syntax.token(name),
back,
TEXT,
)
})
});
let focus = ["bg", "surface"]
.into_iter()
.map(|back| check("focus".into(), palette.focus, back, MARK));
@@ -92,10 +105,10 @@ fn audit(palette: &Palette) -> Vec<Check> {
MARK,
)
});
text.chain(focus).chain(hues).collect()
text.chain(code).chain(focus).chain(hues).collect()
}
/// Audits the theme's contrast in the mode shown: each text color over the surfaces it sits on at 4.5:1, the focus ring and the chart hues beside the page at 3:1, failures first under a count. It reads the theme as it stands, an owner's palette and high contrast included.
/// Audits the theme's contrast in the mode shown: each text color over the surfaces it sits on and each code color over the editor's surfaces at 4.5:1, the focus ring and the chart hues beside the page at 3:1, failures first under a count. It reads the theme as it stands, an owner's palette and high contrast included.
#[derive(IntoElement, Default)]
pub struct A11yChecker;
@@ -206,7 +219,12 @@ mod tests {
let checks = audit(&palette);
let subtle: Vec<&Check> = checks.iter().filter(|c| c.front == "fg_subtle").collect();
assert!(subtle.iter().all(|check| check.grade() == "Fails"));
assert_eq!(checks.len(), 41, "31 text pairs, 2 for the ring, 8 hues");
assert_eq!(
checks.len(),
81,
"32 text pairs, 39 for code, 2 for the ring, 8 hues"
);
assert_eq!(subtle.len(), 6);
}
#[test]
@@ -225,6 +243,27 @@ mod tests {
assert_eq!(grade("fg_muted"), Some("Fails"), "text at 3.3:1");
}
#[test]
fn code_and_hues_read_their_own_colors() {
let mut palette = Palette::light(false);
palette.syntax.comment = palette.surface;
let checks = audit(&palette);
let failing: Vec<&str> = checks
.iter()
.filter(|check| !check.passes())
.map(|check| check.back)
.collect();
assert_eq!(
failing,
["surface", "sunken", "hover"],
"the comment over code"
);
for (ix, name) in crate::theme::HUE_NAMES.iter().enumerate() {
let hue = checks.iter().find(|c| c.front == format!("chart {name}"));
assert_eq!(hue.map(|check| check.fg), Some(palette.chart[ix]));
}
}
#[test]
fn translucent_text_counts_as_it_shows() {
let mut palette = Palette::light(false);
+52 -6
View File
@@ -37,8 +37,47 @@ fn the_viewer_names_each_token_with_its_value(cx: &mut TestAppContext) {
"token-colors.hue(0) Blue-{}",
hex(Rgba::from(light.chart[0]))
);
let measures: [(&str, &[&str], &[u32]); 7] = [
(
"TextSize::",
&["Xs", "Sm", "Base", "Md", "Lg", "Xl", "Xxl", "Display"],
&[11, 12, 13, 14, 16, 20, 24, 32],
),
("Radius::", &["Sm", "Md", "Lg", "Xl"], &[4, 6, 8, 12]),
("ControlSize::", &["Sm", "Md", "Lg"], &[24, 28, 32]),
(
"IconSize::",
&["Xs", "Sm", "Md", "Lg", "Xl", "Xxl"],
&[12, 14, 16, 20, 24, 32],
),
(
"AvatarSize::",
&["Xs", "Sm", "Md", "Lg", "Xl"],
&[20, 24, 32, 40, 56],
),
("ContainerSize::", &["Sm", "Md", "Lg"], &[640, 960, 1200]),
(
"gap_",
&["1", "2", "3", "4", "6", "8"],
&[4, 8, 12, 16, 24, 32],
),
];
for (kind, names, sizes) in measures {
for (name, size) in names.iter().zip(sizes) {
let selector = format!("token-{kind}{name}-{size}");
assert!(shown(&selector, cx), "{selector}");
}
}
for level in ["Raised", "Floating", "Modal"] {
assert!(shown(&format!("token-Elevation::{level}"), cx), "{level}");
}
for (ix, name) in [(0, "black"), (15, "bright white")] {
let code = hex(Rgba::from(light.ansi[ix]));
let selector = format!("token-colors.ansi[{ix}] {name}-{code}");
assert!(shown(&selector, cx), "{selector}");
}
for selector in [
"token-colors.fg_subtle-#6f6d6a",
"token-colors.fg_subtle-#696764",
&keyword,
&blue,
"token-TextSize::Sm-12",
@@ -58,7 +97,7 @@ fn the_viewer_names_each_token_with_its_value(cx: &mut TestAppContext) {
});
cx.run_until_parked();
assert!(
shown("token-colors.fg_subtle-#8c8a87", cx),
shown("token-colors.fg_subtle-#94928f", cx),
"the dark palette"
);
let compact = cx.update(|window, cx| {
@@ -85,16 +124,23 @@ fn the_checker_counts_failures_first(cx: &mut TestAppContext) {
cx.update(Theme::init);
let (_, cx) = cx.add_window_view(|_, _| Audit);
cx.run_until_parked();
assert!(shown("contrast-summary-41-0", cx), "every pair passes");
assert!(shown("contrast-summary-81-0", cx), "every pair passes");
assert!(shown("contrast-fg-bg-AAA", cx));
assert!(shown("contrast-fg_subtle-hover-AA", cx), "4.53:1 on hover");
assert!(
shown("contrast-fg_subtle-active-AA", cx),
"a selected row's detail"
);
assert!(
shown("contrast-syntax.comment-hover-AA", cx),
"a comment on the current line"
);
assert!(shown("contrast-focus-surface-AA", cx));
assert!(shown("contrast-chart Blue-bg-AA", cx));
cx.update(|_, cx| Theme::update(cx, |theme| theme.colors.fg_subtle = theme.colors.bg));
cx.run_until_parked();
assert!(
shown("contrast-summary-41-5", cx),
"five surfaces under fg_subtle"
shown("contrast-summary-81-6", cx),
"six surfaces under fg_subtle"
);
let failing = cx
.debug_bounds("contrast-fg_subtle-hover-Fails")
+44 -3
View File
@@ -1,13 +1,14 @@
use gpui::{
AnyElement, App, FontWeight, Hsla, InteractiveElement, IntoElement, ParentElement, Pixels,
Rems, RenderOnce, Rgba, Styled, Window, div, rems,
Rems, RenderOnce, Rgba, Styled, Window, div, relative, rems,
};
use crate::{
forms::hex,
primitives::{Icon, IconName},
theme::{
ActiveTheme, ControlSize, Elevation, HUE_NAMES, IconSize, Palette, Radius, Syntax, TextSize,
ActiveTheme, AvatarSize, ContainerSize, ControlSize, Elevation, HUE_NAMES, IconSize,
Palette, Radius, Syntax, TextSize,
},
typography::{Ellipsis, literal, tabular},
};
@@ -114,7 +115,7 @@ fn measure(name: String, size: Pixels, sample: impl IntoElement, cx: &App) -> An
.into_any_element()
}
/// Every token of the theme in the mode shown, each named as code reads it: the colors with their hex, the syntax, chart and terminal colors, the type scale, radii, control and icon sizes, elevations and gpui's spacing steps.
/// The theme's shared colors and scales in the mode shown, each named as code reads it: every palette, syntax, chart and terminal color with its hex, and the type, radius, control, icon, avatar and container sizes, the elevations and gpui's spacing steps. A chapter's own measures stay with the chapter.
#[derive(IntoElement, Default)]
pub struct DesignTokenViewer;
@@ -213,6 +214,44 @@ impl RenderOnce for DesignTokenViewer {
.bg(colors.surface);
measure(format!("ControlSize::{name}"), at(height), sample, cx)
});
let avatars = [
(AvatarSize::Xs, "Xs"),
(AvatarSize::Sm, "Sm"),
(AvatarSize::Md, "Md"),
(AvatarSize::Lg, "Lg"),
(AvatarSize::Xl, "Xl"),
]
.map(|(size, name)| {
let side = theme.avatar_size(size);
let sample = div()
.size(side)
.rounded_full()
.border_1()
.border_color(colors.border_strong)
.bg(colors.sunken);
measure(format!("AvatarSize::{name}"), at(side), sample, cx)
});
let containers = [
(ContainerSize::Sm, "Sm"),
(ContainerSize::Md, "Md"),
(ContainerSize::Lg, "Lg"),
]
.map(|(size, name)| {
let width = theme.container_width(size);
let widest = theme.container_width(ContainerSize::Lg);
// Drawn at its share of the widest, so the bar stays inside the row.
let sample = div().relative().flex_1().h_2().child(
div()
.absolute()
.top_0()
.left_0()
.h_full()
.w(relative(width.0 / widest.0))
.rounded(theme.radius(Radius::Sm))
.bg(colors.border_strong),
);
measure(format!("ContainerSize::{name}"), at(width), sample, cx)
});
let icons = [
(IconSize::Xs, "Xs"),
(IconSize::Sm, "Sm"),
@@ -276,6 +315,8 @@ impl RenderOnce for DesignTokenViewer {
.child(group("Radii", cx).children(radii))
.child(group("Controls", cx).children(controls))
.child(group("Icons", cx).children(icons))
.child(group("Avatars", cx).children(avatars))
.child(group("Containers", cx).children(containers))
.child(
group("Elevation", cx)
.child(div().flex().flex_wrap().gap_6().p_2().children(elevations)),
+2 -2
View File
@@ -366,7 +366,7 @@
## T43c Tooling · Catalogs
- [x] DesignTokenViewer → `tooling::DesignTokenViewer`: every token of the theme in the mode shown, named as code reads it, with its value
- [x] DesignTokenViewer → `tooling::DesignTokenViewer`: the theme's shared colors and scales in the mode shown, named as code reads them, with their values
- [x] IconBrowser → `forms::IconPicker`: the library's icons, searchable
- [x] A11yChecker — contrast audit → `tooling::A11yChecker`: each text color over its surfaces at 4.5:1, the focus ring and chart hues beside the page at 3:1, failures first
- [x] A11yChecker — contrast audit → `tooling::A11yChecker`: each text color over its surfaces and each code color over the editor's at 4.5:1, the focus ring and chart hues beside the page at 3:1, failures first
- [x] ScreenshotTest Harness — self-capture; scripted input since T01 → `examples/gallery --capture`: every page light and dark, then its scripted states