TacidOS
Developers
Documentation

Decision 0087

0087. A buffer with no alpha channel decides what the face can look like

Architecture decision record 0087: 0087. A buffer with no alpha channel decides what the face can look like

Status
superseded
Date
2026-09-10

Status: superseded by ADR-0088, which checked this record’s three reasons for not switching renderer and found none of them standing. What is still true here is the finding: softbuffer hands Wayland a buffer with no alpha channel, and a surface drawn at an opacity that buffer cannot carry arrives darkened with black corners. What is wrong is everything decided from it. Refines ADR-0047, whose renderer choice this record does not overturn, and bounds one sentence of ADR-0021.

Context

The design tokens gained a [glass] block: an opacity, a blur, a shadow. tacid-design emits it into the compositor’s decoration and layerrule blocks and into the notification daemon’s colours, and tacid-shell drew every container at that opacity. The compositor blurs what is behind a Tacid layer surface; the surface draws at less than full alpha so there is something to blur. That is glass, and it was wrong in a way nothing in the tree could have caught.

checks.*.face photographs the shell over two terminals. Three measurements, each taken after fixing what the last one showed:

A command box over a terminal was srgb(19,25,30). The surface colour at glass.opacity over the terminal’s srgb(40,85,119) is (23,34,43); over the toolkit’s own background it is (19,25,30), to the byte. Iced fills the window with the theme’s background before any container draws, so every container’s alpha was blending against a colour iced chose. Fixed by giving each surface a window that paints nothing.

Then it was srgb(17,22,27) at every point, whatever was behind it. 0.86 × (20,26,31): the colour premultiplied by its own alpha and composited over black. And the rounded corners — pixels deliberately left clear so the desktop would show — came back rgb(0,0,0). Four black notches, unnoticeable at a glance because the desktop behind them is nearly black.

The cause is a constant in a dependency. tiny-skia reaches Wayland through softbuffer, whose create_buffer call names wl_shm::Format::Xrgb8888 — no alpha channel, hardcoded, no argument, no fallback. Every surface this shell hands a compositor is opaque by construction.

ADR-0047 chose the software renderer for three reasons that all still hold: a face must come up in a virtual machine, on a server’s remote display, and on the first boot after a driver change. None of them mentioned what the choice costs in appearance, because nobody knew.

Decision

The shell asks whether alpha reaches the compositor, and draws opaque when it does not. ALPHA_SURVIVES in theme.rs is that question. Today the answer is false for every build, and three things follow from it rather than being decided separately:

  • Skin::glass is 1.0 instead of the token’s opacity, so a surface is its own colour rather than that colour darkened by the fraction that could not show through.
  • Skin::window paints the palette’s background instead of nothing, because on a buffer with no alpha channel an unpainted pixel is not the desktop, it is black.
  • The command box fills its layer surface instead of hugging its content, because the rest of a hugged surface is unpainted, and unpainted is a slab of background over somebody’s windows rather than the windows.

Glass outside the shell stays. The compositor rounds windows at the token radius, blurs behind Tacid’s layer namespaces and mako’s, and draws shadows; the notification daemon takes the opacity as #RRGGBBAA; the lock screen takes the control corner. Those are generated files held to their own programs’ parsers by checks.*.session, and they are correct on hardware today. The shell is the one surface that does not join in, and saying that plainly is better than a token file whose value reaches four consumers and is quietly dropped by the fifth.

The check measures the shell’s opacity rather than asserting its translucency. The sheet’s own colour must be the palette’s surface exactly, which fails on a surface darkened by an alpha it could not carry; and a rounded corner must not be black while the desktop behind it is not. Both assertions fail on the two states above, and both are statements about a shell that is honest about having no glass rather than about glass.

Consequences

The face is rounded, shadowed by the compositor, and opaque in its own surfaces. On a machine with a GPU, windows are rounded and blurred and Tacid’s own strip is not — which is a visible inconsistency and the honest state of it.

ALPHA_SURVIVES is a compile-time answer to what will become a runtime question. Adding iced/wgpu beside tiny-skia does not make it true: iced falls back to tiny-skia on a machine with no working GPU, and there the buffer is Xrgb8888 again. Whoever adds the second renderer owns asking which one the process actually got, and the shell’s Cargo file says so where they will be reading.

The token file keeps a [glass] block one consumer ignores. That is the right shape: the value is a property of the design, the ignoring is a property of one renderer, and moving the value out would mean putting it back when the renderer changes.

Alternatives rejected

Draw the opacity anyway and call it glass without blur. It is not that. It is the same colour fourteen percent darker with black notches at its corners, and it looks deliberate, which is worse than looking wrong.

Switch to wgpu now. It answers the appearance question and reopens all three of ADR-0047’s: several hundred crates, a graphics stack that must work before a pixel is drawn, and no face on the one boot where a person most needs to see what their machine thinks is wrong. And it would not be verifiable here — this environment has no GPU, so a wgpu build falls back to the renderer whose limitation this record is about, and the glass would ship unmeasured.

Patch or fork softbuffer to ask for Argb8888. A fork of a rendering dependency, carried forever, for a visual effect. The upstream constant may well change; this record does not need it to.

Drop [glass] from the tokens. Four consumers use it correctly today. Removing a value because a fifth cannot is deciding the design by the weakest renderer.

Leave it and let somebody notice. They would not have. Every photograph looked plausible; the first two states were found by arithmetic on pixels, not by looking.