Skip to main content

max / alloy

Fix which ANSI slots the greeter names The tuigreet --theme string had `time` and `action` at dark_gray, which is slot 8 and content.muted, drawn onto the console background at slot 0. Slot 0 is the darkest tone in either polarity, so that is muted grey on near-black: a contrast ratio under 2:1, and it left the clock and the F-key hints all but invisible on the first screen of a boot. The cause is that the screen has two surfaces and the string was written as if it had one. tuigreet paints a background for exactly one element, the login card (`container`); the clock above it and the status line below it sit directly on the console. So elements outside the card have to name a light tone and elements inside it a dark one, and getting that backwards is invisible when you write the string and obvious the moment it draws. `time` and `action` move to white (slot 15, surface.overlay). `container` moves from white to gray, which is slot 7 and is what skelgen's achromatic table calls the login card, so the card reads as a raised surface rather than as the lightest tone available. `prompt` and `button` move from yellow to light_red: yellow is slot 3, status.warning, which does not belong on a prompt, and it sat under 3:1 on the card where the accent at slot 9 clears 5:1. Every slot named is achromatic or the accent, so the whole string inverts with the theme and reads the same way against the night table. `title` follows `border` in tuigreet and `greet` follows `text`, so neither needs naming. The doc had the same mistake in a different form: it said tuigreet asks for `white` and gets `surface.page`, which is two errors, since white is slot 15 and carries surface.overlay. ratatui's numbering is what tuigreet inherits by handing each value to `Color::from_str`, and there gray is 7 and white is 15, not the other way around.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-30 22:00 UTC
Signed with PGP, not checked
Commit: e9a7a7b7f7a11bb9a80a72dc3cd10ba19b4569ca
Parent: fd869fb
2 files changed, +32 insertions, -4 deletions
M docs/STACK.md +3 -1
@@ -449,7 +449,9 @@
449 449
450 450 **greetd + tuigreet.** Rust, minimal, ratatui-rendered. greetd is the daemon that owns VT1; tuigreet is the ratatui client that prompts for user/password and execs [`alloy-session`](../usr/bin/alloy-session) on successful auth, which applies the chosen day/night skeleton and execs `sway`. Config at [`etc/greetd/`](../etc/greetd/): `/etc/greetd/config.toml` sets up VT1 with tuigreet and a `--theme` written in ANSI color names.
451 451
452 - **The palette reaches it through the console, not through `--theme`.** tuigreet 0.9.1 parses only the sixteen ANSI names; a hex value is ignored rather than rejected, and the affected element falls back to a default, so `prompt=#8a4530` renders red. This doc claimed an Akari greeter for a while on the strength of a `--theme` that could not deliver one. What delivers it is [`usr/lib/bootc/kargs.d/10-alloy.toml`](../templates/usr/lib/bootc/kargs.d/10-alloy.toml.in), which sets the Linux console's 16-color table via `vt.default_red/grn/blu`. tuigreet then asks for `white` and gets `surface.page`. The same substitution themes every VT, including the debug shell and any getty, and it costs one boot-time kernel argument rather than a patch to tuigreet.
452 + **The palette reaches it through the console, not through `--theme`.** tuigreet 0.9.1 parses only the sixteen ANSI names; a hex value is ignored rather than rejected, and the affected element falls back to a default, so `prompt=#8a4530` renders red. This doc claimed an Akari greeter for a while on the strength of a `--theme` that could not deliver one. What delivers it is [`usr/lib/bootc/kargs.d/10-alloy.toml`](../templates/usr/lib/bootc/kargs.d/10-alloy.toml.in), which sets the Linux console's 16-color table via `vt.default_red/grn/blu`. tuigreet then asks for `gray` and gets `surface.raised`. The same substitution themes every VT, including the debug shell and any getty, and it costs one boot-time kernel argument rather than a patch to tuigreet.
453 +
454 + **Which name lands on which slot is the whole design.** tuigreet hands each `--theme` value to ratatui's `Color::from_str`, so the names carry ratatui's numbering: `gray` is slot 7 and `white` is slot 15, not the other way around. Only the login card gets a background (`container`); the clock and the status line are drawn onto the console, whose background is slot 0. Elements outside the card therefore have to name a light tone and elements inside it a dark one, which the string in `config.toml` now does and did not before.
453 455
454 456 Same ratatui toolkit family as `alloy console` and `bottom`; the login screen reads as a design sibling to the rest of the Alloy chrome.
455 457
@@ -5,11 +5,37 @@
5 5 # skeleton in place and execs sway. Pointing --cmd at bare sway would
6 6 # start a session against whatever skeleton happens to be on disk, and
7 7 # ~/.config/sway/config is one of the files the mode changes.
8 - # tuigreet's --theme below names ANSI slots (white,
9 - # yellow, dark_gray ...); those slots carry Akari's tones because
8 + # tuigreet's --theme below names ANSI slots (gray, black,
9 + # light_red ...); those slots carry Akari's tones because
10 10 # alloy-vtrgb.service repaints the console palette with setvtrgb before
11 11 # this greeter draws. The named colors here are meaningless without it.
12 12 #
13 + # The screen has two surfaces, and every element belongs to one of them.
14 + # The login card is the only thing tuigreet paints a background for
15 + # (`container`, a bg); the clock above it and the status line below it
16 + # are drawn straight onto the console, whose background is slot 0 and
17 + # whose default text is slot 7. Slot 0 is the darkest tone in either
18 + # polarity, so anything outside the card has to be a light tone and
19 + # anything inside it a dark one. Getting that backwards is what the
20 + # earlier string did: `time` and `action` were dark_gray (slot 8,
21 + # content.muted) on slot 0, a ratio under 2:1, which left the clock and
22 + # the F-key hints all but invisible on the first screen of a boot.
23 + #
24 + # The slot names, per skelgen's ANSI_16 under a light theme:
25 + #
26 + # gray (7) surface.raised, the login card
27 + # white (15) surface.overlay, the lightest tone
28 + # black (0) content.primary, the ink, and the console background
29 + # dark_gray (8) content.muted
30 + # light_red (9) action.primary, the theme's warm accent
31 + #
32 + # All five are achromatic slots or the accent, so they invert with the
33 + # theme and this string reads the same way against the night table.
34 + # `yellow` is status.warning and does not belong on a prompt; the accent
35 + # is what an interactive element wants, and it clears 5:1 on the card
36 + # where yellow sat under 3:1. `title` follows `border` and `greet`
37 + # follows `text` in tuigreet, so neither needs naming here.
38 + #
13 39 # Command must stay on a single line: greetd's TOML parser rejects the
14 40 # multi-line basic-string / backslash-continuation form ("expected
15 41 # equals sign on line, but found none").
@@ -19,5 +45,5 @@
19 45 switch = true
20 46
21 47 [default_session]
22 - command = "tuigreet --time --remember --asterisks --cmd alloy-session --theme 'container=white;border=dark_gray;text=black;time=dark_gray;prompt=yellow;input=black;action=dark_gray;button=yellow'"
48 + command = "tuigreet --time --remember --asterisks --cmd alloy-session --theme 'container=gray;border=dark_gray;text=black;time=white;prompt=light_red;input=black;action=white;button=light_red'"
23 49 user = "greeter"