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:
1 parent
d5271d03ea
commit
a7e492f28e
7 files changed
+153
-27
No files matched your search
@@ -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.
|
||||
@@ -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()))
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
Reference in new issue
Block a user