Skip to main content

max / alloy

9.0 KB · 146 lines History Blame Raw
1 # Alloy Tokens
2
3 The design-token system for Alloy. Storage format, authoring discipline, extension rules, and how Alloy renders any theme downloaded from the future theme catalog.
4
5 The rules these tokens implement live in [DESIGN-LANGUAGE.md]DESIGN-LANGUAGE.md. This file is where the rules become storage, derivation, and gates.
6
7 ## Storage format: makeover
8
9 Alloy consumes themes in the [makeover]https://makenot.work/git/max/makeover TOML format, the same schema GoingsOn, Balanced Breakfast, audiofiles, and makenot.work already consume. A theme file has seven sections:
10
11 ```toml
12 [meta]
13 name = "Akari Dawn"
14 variant = "light" # "light" | "dark" | "high-contrast"
15
16 [surface]
17 page = "#e4ded6" # main canvas
18 raised = "#ede7de" # elevated / focused / popup base (lighter than page in light mode)
19 sunken = "#cfc4b6" # recessed panels (darker than page in light mode)
20 overlay = "#f0ece4" # modal / top-most temporary UI
21
22 [content]
23 primary = "#1a1816" # body text
24 secondary = "#222d38" # section headers, labels
25 muted = "#514b45" # disabled, placeholder, metadata
26
27 [action]
28 primary = "#8a4530" # the theme's primary action / brand accent
29
30 [status]
31 danger = "#6a2828" # error
32 success = "#3a5830" # healthy / ok
33 warning = "#b07840" # near-threshold
34 info = "#304050" # informational / link / in-progress
35
36 [line]
37 border = "#cabeae" # single border tone; Alloy derives -subtle and -strong (see below)
38
39 [category]
40 one = "#8a4530" # categorical series colors (chart / syntax / tag rows)
41 two = "#3a5830"
42 three = "#304050"
43 four = "#b07840"
44 five = "#806080"
45 six = "#305858"
46 ```
47
48 Alloy's default light theme is **[Akari Dawn]https://makenot.work/git/max/makeover/tree/main/themes/akari-dawn.toml**; the default dark is **[Akari Night]https://makenot.work/git/max/makeover/tree/main/themes/akari-night.toml**. Both are based on Shu Kutsuzawa's [Akari]https://github.com/cappyzawa/akari-theme (MIT). A mode is picked in `alloy settings`, which writes two things: the console's own theme key, effective immediately, and `~/.config/alloy/mode`, which every other themed config follows at the next login via `alloy theme apply`. A console told nothing follows what it is drawn on instead, asking `$COLORFGBG` first and the mode file second. `--theme` overrides either for one run. Users can also drop any makeover `.toml` into `~/.config/alloy/themes/` and name it.
49
50 There is no OSC background query. It is the only other way to learn a terminal's background, and it means writing an escape sequence and waiting for a reply before the first frame, which is not worth the stall.
51
52 **Why makeover as the storage format:** the future user-facing theme catalog (a website of downloadable `.toml` files) is only useful if every make-family app renders any downloaded theme the same way. Sharing the format across apps buys that with zero adapter code.
53
54 ## Alloy-specific derived tokens
55
56 Alloy needs three token tiers makeover ships one of: `border-subtle`, `border`, `border-strong`. Rather than extend the schema (which would break existing consumers), Alloy computes the outer two from `line.border` at load time:
57
58 ```
59 border-subtle = mix(line.border, surface.page, 60%) # pulled toward surface — decorative divider
60 border-strong = mix(line.border, content.primary, 65%) # pulled toward text — focus rings, selection
61 ```
62
63 Mix is in linear sRGB. The 65%-toward-text ratio for `border-strong` is calibrated to reach WCAG AA-UI (≥3.0:1) against `surface.page` even on soft-border themes (Akari's `line.border` is `#cabeae`; the mix produces `#7f786d` which lands at 3.27:1). Themes with crisper borders will overshoot; that's fine.
64
65 The formulas live in `alloy_tui`, not in the theme file. Theme files stay minimal and cross-app compatible.
66
67 ## Authoring discipline: OKLCH mental model, hex on disk
68
69 Themes on disk are hex. The design *reasoning* behind those hex values (Alloy-authored ones or curated ones like Akari) still uses OKLCH:
70
71 - **Ramp discipline.** Surface tiers should follow monotonic L ordering per mode (light: `sunken < page < raised < overlay`; dark: inverted). Accents live in the mid-L / mid-C range where they read as color-carrying-information on either ramp.
72 - **Tinted-not-neutral chrome.** Chrome tones should carry a hue tint (see [DESIGN-LANGUAGE.md]DESIGN-LANGUAGE.md#core-principle-tinted-greyscale-chrome-color-as-information); Akari's warm-clay palette is the canonical worked example.
73 - **Accent-on-glyph, not on body text.** Confirmed by contrast math in the audit: accents that clear WCAG AA-text (4.5:1) on the theme's surface can be used freely; accents that only clear AA-UI (3.0:1) go on glyphs and edge markers, with body text staying at `content.primary`.
74
75 Because storage is hex, OKLCH doesn't need to be preserved in the file; it's the mental model of the person authoring the theme.
76
77 ## Gate: the WCAG audit
78
79 Any theme, downloaded or authored, is audited by [`tools/wcag_audit.py`]../tools/wcag_audit.py. It reads a makeover `.toml`, converts hex to linear sRGB to WCAG 2.1 relative luminance, and reports pass/fail for every affordance-carrying pair (text-on-surface, borders-on-surface, accents-on-surface, elevation deltas). Also computes `border-subtle` / `border-strong` via the same derivation Alloy uses at runtime.
80
81 Run:
82
83 ```
84 python3 tools/wcag_audit.py ../Libraries/makeover/themes/akari-dawn.toml
85 ```
86
87 The audit is not a hard-fail gate: a theme with accents that only reach AA-UI on some surface tiers is still shippable if the accent-on-glyph rule is honored. The audit's job is to make trade-offs visible.
88
89 ## Verified contrast: Akari Dawn
90
91 Method: OKLCH-agnostic; reads hex, computes WCAG 2.1 relative luminance directly.
92
93 **Text on surfaces:**
94
95 | Pair | vs sunken | vs page | vs raised | vs overlay |
96 |---|---:|---:|---:|---:|
97 | `content.primary` | 10.31 | 13.25 | 14.40 | 15.03 |
98 | `content.secondary` | 8.15 | 10.48 | 11.39 | 11.88 |
99 | `content.muted` | 5.01 | 6.44 | 7.00 | 7.30 |
100
101 All twelve pairings clear AA-text (4.5); most clear AAA (7).
102
103 **Borders on surfaces:**
104
105 | Pair | vs page | vs raised | vs overlay |
106 |---|---:|---:|---:|
107 | `border-strong` (derived) | 3.27 | 3.55 | 3.71 |
108 | `line.border` | 1.37 | 1.49 | 1.55 |
109 | `border-subtle` (derived) | 1.12 | 1.22 | 1.27 |
110
111 `border-strong` clears AA-UI on all three surfaces; the derivation formula works for Akari's soft border. `line.border` and `border-subtle` are decorative dividers, not required to meet 3.0.
112
113 **Accents on surfaces:**
114
115 | Accent | vs page | vs raised | vs sunken | vs overlay | Notes |
116 |---|---:|---:|---:|---:|---|
117 | `action.primary` (lantern `#8a4530`) | 5.28 | 5.74 | **4.11** | 5.99 | AA-text on three of four; AA-UI on `sunken`, glyph-safe on the darkest tier |
118 | `status.danger` (`#6a2828`) | 8.07 | 8.78 | 6.28 | 9.15 | AAA everywhere |
119 | `status.success` (`#3a5830`) | 6.00 | 6.53 | 4.67 | 6.81 | AA-text everywhere |
120 | `status.warning` (`#b07840`) | **2.80** | **3.05** | **2.18** | **3.18** | Fails AA-text on all tiers; AA-UI only on `raised`/`overlay`. **Amber-on-cream is inherent to Akari's aesthetic**; per the accent-on-glyph rule, warning is expressed as glyph + edge marker only. |
121 | `status.info` (`#304050`) | 7.96 | 8.65 | 6.19 | 9.03 | AAA / AA-text everywhere |
122
123 Both failure modes are Akari-inherent rather than derivation bugs, and both survive Alloy's discipline: `action.primary` on `surface.sunken` still clears AA-UI (glyph-safe on the darkest recessed panel), and `status.warning`'s amber is a glyph-only accent.
124
125 ## Verified contrast: Akari Night
126
127 Text on surfaces all AAA or AA-text. Borders derived at 7+ contrast (dark themes have more headroom because the border tone is against a light content.primary). Notable accent behavior: `status.danger` at `#d25046` lands at 3.3–4.1:1 across surfaces: AA-UI territory, glyph-safe under the same rule. Full audit reproducible via the script.
128
129 ## What's deferred
130
131 - **Calibrated-display verification** on real hardware in real terminals (truecolor and 256-color). Pre-1.0 gate.
132 - **256-color downgrade table** for terminals that report no truecolor.
133 - **Figlet / block-element recipes** for the large numeric readouts a display font would otherwise carry.
134 - **Swapping the desktop's mode without a re-login.** `alloy theme apply` ships and the settings row writes the mode file, but the configs it lays down are read once by programs already running, so the switch lands at the next login. The console itself re-themes immediately.
135
136 ## How this file is consumed
137
138 The themes [makeover ships]https://makenot.work/git/max/makeover/tree/main/themes/ are the ground truth. `alloy_tui` at load time:
139
140 1. Reads the selected theme file via the `makeover` crate.
141 2. Computes the two derived tokens (`border-subtle`, `border-strong`) from `line.border`.
142 3. Exposes the full token map as a runtime `Theme` of ratatui `Style` / `Color` values.
143 4. Optionally emits a 256-color fallback table for terminals without truecolor.
144
145 No hex values are hard-coded in Rust. Changing the theme file changes every authored Alloy surface after re-launch (or hot-swap once `alloy theme` ships). Third-party themes downloaded into `~/.config/alloy/themes/` work with zero adapter code.
146