TacidOS
Developers
Documentation

Decision 0089

0089. The boot screen is generated from the tokens, like every other surface

Architecture decision record 0089: 0089. The boot screen is generated from the tokens, like every other surface

Status
accepted
Date
2026-09-10

Status: accepted. Extends ADR-0021, which says every surface’s colours come from one file, to the one surface that had never been in it.

Context

design/tokens.toml decides what a Tacid machine looks like, and tacid-design writes every file that carries a colour: the compositor’s borders and gaps, the notification daemon’s, the lock screen’s, the terminal palettes, the toolkit themes, and the sixteen colours of the virtual console, so that a machine with no screen still has this machine’s face.

The boot was not one of them. A machine that had just been switched on drew white kernel messages on a black console until systemd reached the greeter: somebody else’s face, and the first one its owner sees, every single time. Underneath that, a Tacid machine encrypts its root by default (ADR-0012), so the very first thing it asks a person for is a passphrase — and it asked for it as a bare line of console text among the udev messages.

This is not only a matter of appearance. MANIFESTO.md §8 says beauty is a requirement and that there is one design system; a surface outside it is the seam that says the machine was assembled rather than designed. And the boot is the surface a person cannot avoid.

The objection to a boot splash is real and has to be answered: hiding the log hides state, and this project’s first principle is that a machine is a value with nothing hidden in it. The answer is that a boot splash moves the log rather than deleting it. Escape brings plymouth’s console viewer up while the splash is running, plymouth.enable=0 on the kernel command line removes the splash for one boot without changing the machine, tacid.desktop.splash = false removes it for good, and journalctl -b has every line either way — which is where anybody was going to read it from. What is quietened is a screen, not a record.

Decision

A machine with a screen boots into a screen of its own, generated from design/tokens.toml by tacid-design like every other surface. tacid.desktop.splash turns it off; it is on by default, and it exists only where tacid.desktop.enable is — a headless machine’s console is its only face and keeps every line of the log.

The theme is text and nothing else. There is no image in the theme directory and none loaded from one. Plymouth’s script plugin can ask pango for a rendered string, and the one font that has to be in the initrd for that is the face this machine is already set in — so the machine’s typography starts at the firmware rather than at the login, and a change to the token file reaches every glyph on the screen. A directory of PNGs would have put part of this surface outside the system on the day it was written.

What is on it, and usually only the first two:

                            tacid
                        tacidd.service
                         • • • • • •
                          caps lock

the machine’s name in the text colour, the unit systemd is starting in the muted one, a dot in the accent for each character typed when a disk wants its key, and a caps-lock warning in the warning colour. The line under the name carries the status and the prompt, in two different colours, because the two are never both true and a screen with a permanently empty slot on it is a screen designed around the code that draws it.

Nothing spins and nothing fills. Plymouth has a progress bar and this does not use it: its progress is scaled by the duration of the previous boot, read from a cache file, so on the boot where it would matter most — the first one — it is fiction. The unit systemd is starting is not fiction. It is the true answer to what the machine is doing, it is spelled the way the journal spells it so it is the string to search for afterwards, and when a boot stops it names the thing that stopped it. That is MANIFESTO.md §8’s “no chrome without purpose” applied to the one screen where the temptation to decorate is strongest.

The view behind it is themed too. ConsoleLogTextColor and ConsoleLogBackgroundColor in the theme descriptor put the details view in the machine’s text colour over a transparent ground, so Escape brings the log up onto the boot screen rather than replacing it with a black rectangle.

checks.*.boot draws it and measures it on every push, in about thirteen seconds. Plymouth has an X11 renderer, so everything above the renderer — the theme lookup, the script parser, the sprite library, the pango labels — can be exercised with an Xvfb and no machine at all. The check asserts the ground to the byte, that the line is the muted colour while the machine is starting and the full text colour while it is asking a person for something, and that dots in the accent appear when a key lands and are absent when one has not. The pair is the point: a check that only looked at the password screen would pass on a theme that drew dots the whole time.

Consequences

Four things were read out of plymouth’s source rather than recalled, and three changed the code.

extract_rgb_color in plymouth’s sprite library multiplies a fraction by 255 and assigns the result into a uint8_t. It truncates. Asking for #0d1013 as 13.0 / 255.0 painted srgb(12,15,18) on a screen — measured, not reasoned about — so every channel is written as the middle of the bucket it means, (value + 0.5) / 255. This is the same class of mistake as the glass measurements in ADR-0088: a colour that arrives one level off looks like a considered choice.

Plymouth exposes the caps-lock state and never announces a change in it, and the keystroke callback does not fire for a key that types no character. So it is polled, at ten times a second, and the body of the callback is one comparison until the state actually changes. A timer is not free and this one is earned on the single screen where a passphrase that will be rejected without a reason is being typed.

Image.Text takes a colour per call, which is what makes one line able to be two things.

MonospaceFont in the descriptor names the interface face and not the mono one. An initrd carries one font file, because that is the shape of the option that puts one there. Naming the mono face would name a file that is not in the initrd and get the one that is, at a size nobody chose.

The check needed a user namespace. plymouthd refuses to start as anybody but root and a Nix build runs as a build user, so it runs under unshare --map-root-user. It really does have euid zero, in a namespace owning exactly one build directory. The client stays outside and reaches it over the abstract socket, which is per network namespace rather than per user one.

The accent test in tacid-design would have exempted both new files in silence. It reads a colour as six hex digits after a #, an rgb( or an rgba(, and plymouth takes colours as three fractions of full — so neither generated file names the accent in a notation that test can see, and both were quietly excused from the rule the whole design system rests on. The exemptions are now named rather than counted, and a surface that joins the list without a reason written beside it fails there.

udev.log_level=3 and not quiet. Both quieten a boot; only one of them is honest about which part. udev’s coldplug is the noise, several hundred lines about devices nobody asked after, and warnings still print. quiet would take the kernel’s own errors down with it, and an error at boot is exactly the thing that should be allowed to interrupt a picture.

It does not extend to the installer’s own boot or to a headless machine. The netinstall boots to a terminal interface where the log is the interface, and a server’s console is the only face it has. This is a surface for machines that have a screen to put it on.

Alternatives rejected

Leaving the boot as it was. It is the position this record overturns, and its one good argument — that a splash hides the log — is answered by the log being one keypress, one kernel parameter and one journalctl away.

A logo and a progress bar, which is what every other distribution ships. The progress bar is fiction on the boot that matters, and a logo is a picture: it would put the largest thing on the screen outside the design system, and it would be the one part of this machine’s face that a change to design/tokens.toml could not reach.

Theming plymouth’s two-step or spinner theme instead of writing one. Both are C plugins configured with images and a handful of keys; neither can put the name of the unit that is starting on the screen, and both would have needed a directory of pictures generated at build time to say anything at all.

Colouring it from Nix rather than from tacid-design. nix/lib/tokens.nix reads the same file and could have written the theme in the module. It would have been a second generator for one surface, with no unit tests, in a language that cannot be asked whether the result parses.