1#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, specta::Type)]
16pub struct Token {
17 pub name: &'static str,
19 pub value: &'static str,
21 pub role: &'static str,
23}
24
25#[rustfmt::skip]
27pub const SIGNAL: &[Token] = &[
28 Token { name: "bg", value: "#070B10", role: "canvas background" },
29 Token { name: "surface", value: "#121A24", role: "raised surface" },
30 Token { name: "surface_2", value: "#1F2B3A", role: "second elevation" },
31 Token { name: "hue", value: "#6BDBFF", role: "accent" },
32 Token { name: "hue_dim", value: "#3FA8CC", role: "accent, dimmed" },
33 Token { name: "hue_bright", value: "#9AE8FF", role: "accent, bright" },
34 Token { name: "fg", value: "#E4EDF5", role: "foreground text" },
35 Token { name: "faint", value: "#526274", role: "faint structure (decorative/hairline only — never text or essential UI)" },
36 Token { name: "muted", value: "#9AA5AF", role: "muted text" },
37 Token { name: "warn", value: "#F2C14E", role: "warning" },
38 Token { name: "fail", value: "#FF6E6E", role: "failure" },
39 Token { name: "ok", value: "#6EE7A8", role: "success" },
40];
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45
46 #[test]
47 fn twelve_unique_snake_named_hex_tokens() {
48 assert_eq!(SIGNAL.len(), 12);
49 let mut names: Vec<&str> = SIGNAL.iter().map(|t| t.name).collect();
50 names.sort_unstable();
51 names.dedup();
52 assert_eq!(names.len(), 12, "duplicate token name");
53 for token in SIGNAL {
54 assert!(
55 token
56 .name
57 .chars()
58 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_'),
59 "token name not snake_case: {}",
60 token.name
61 );
62 assert!(
63 token.value.len() == 7
64 && token.value.starts_with('#')
65 && token.value[1..].chars().all(|c| c.is_ascii_hexdigit()),
66 "token value not #RRGGBB: {}",
67 token.value
68 );
69 assert!(!token.role.is_empty());
70 }
71 }
72
73 #[test]
74 fn faint_role_forbids_text_and_essential_ui() {
75 let faint = SIGNAL
76 .iter()
77 .find(|token| token.name == "faint")
78 .expect("faint token");
79 assert_eq!(
80 faint.role,
81 "faint structure (decorative/hairline only — never text or essential UI)"
82 );
83 }
84
85 #[test]
86 fn token_serializes_as_plain_data() {
87 let json = serde_json::to_value(SIGNAL[0]).expect("serialize");
88 assert_eq!(
89 json,
90 serde_json::json!({ "name": "bg", "value": "#070B10", "role": "canvas background" })
91 );
92 }
93}