Skip to content

fix(ui): every modal dims the screen the same way, and with the theme - #81

Merged
youhide merged 1 commit into
mainfrom
t3-scrim-and-states
Aug 21, 2026
Merged

fix(ui): every modal dims the screen the same way, and with the theme#81
youhide merged 1 commit into
mainfrom
t3-scrim-and-states

Conversation

@youhide

@youhide youhide commented Aug 21, 2026

Copy link
Copy Markdown
Owner

T3 — and the premise did not hold.

The plan called the ad-hoc alphas "the de-facto elevation system". Measured: 14 distinct alphas across 32 sites in 13 files, and there is no elevation system for them to be — hideGit draws no shadows anywhere. A modal is told apart from the screen by its scrim and its own surface colour, not by depth.

They are four unrelated jobs sharing a spelling: a scrim, an interaction state, a background wash, and fading something out. Reading them that way is what turned up the defect below.

The defect

Six places dim the screen behind a modal.

Where Scrim
plan.rs, palette.rs, shortcuts.rs, settings.rs, search.rs palette.background at 0.75
overlay.rs Color::BLACK at 0.55

The odd one out is overlay, which draws every confirmation, action sheet, prompt and device-code dialog. So on the light theme, the modals people see most were the only things on screen dimmed with black, while a plan editor or a command palette dimmed it with the page.

Palette::selection already records the general form of this — "an alpha tuned on a dark background does not transfer", which is why the selection colours are opaque per-theme values. The scrim was the case that had not learned it.

What lands

theme::scrim(palette) is the one way to dim the screen, used by all six. HOVERED and DISABLED are named for the same reason one step smaller: a control dimmed 0.5 in the plan editor and 0.35 in a dialog is the same drift. Both outliers corrected.

How it is pinned

The scrim's test asserts it differs between the two palettes — which Color::BLACK could never satisfy, whatever its alpha. That is the invariant, not the number.

The relations between the three constants are compile-time assertions, not tests:

const _: () = assert!(DISABLED < HOVERED, "hovering must not look more off than being disabled");
const _: () = assert!(SCRIM > DISABLED, "the screen behind a modal is hidden further than a control is dimmed");

I wrote them as a test first and clippy caught it, exactly as in #80: a runtime assertion over constants proves nothing about a run, and a wrong value should fail the build.

Sabotage — three, all confirmed

Sabotage What broke
the scrim goes back to Color::BLACK the_scrim_follows_the_theme_rather_than_being_black
DISABLED becomes more opaque than HOVERED compile error
SCRIM drops below DISABLED compile error

What is deliberately left

The background washesaccent at 0.12, border at 0.5, and the rest. Those are the case Palette::selection is actually about: they need opaque per-palette colours rather than a named alpha, and picking those colours is the colour-roles work later in the plan, not a token rename.

Screenshots

None retaken. The four committed ones show no modal, and the one scrim visible among them — the command palette's — already used palette.background and is unchanged. The change is visible on a confirmation dialog in the light theme, which none of them contain; the palette-differs test is what stands in for that, and it is a stronger check than an image.

Gate

cargo fmt --all -- --check, cargo clippy --workspace --all-targets --all-features --locked -- -D warnings, cargo test --workspace --all-features --locked — 896 tests, all green.

T3 was planned as "the 16 ad-hoc alphas are the de-facto elevation system".
Measured, that premise does not hold. There are 14 distinct alphas across 32
sites, and there is no elevation system for them to be: hideGit draws no shadows
anywhere, and a modal is told apart from the screen by its scrim and its own
surface colour rather than by depth.

What they are is four unrelated jobs sharing a spelling — a scrim, an
interaction state, a background wash, and fading something out. Reading them
that way turned up a real defect.

Six places dim the screen behind a modal. Five build the scrim from
`palette.background` at 0.75. The sixth used `Color::BLACK` at 0.55 — and the
sixth is `overlay`, which draws every confirmation, action sheet, prompt and
device-code dialog. So on the light theme, the modals people see most were the
only things on screen dimmed with black, while a plan editor or a command
palette dimmed it with the page.

`Palette::selection` already records the general form of this: an alpha tuned on
a dark background does not transfer to a light one, which is why the selection
colours are opaque per-theme values. The scrim was the case that had not learned
it.

So `theme::scrim` is the one way to dim the screen, and `HOVERED` and `DISABLED`
are named for the same reason one step smaller — a control dimmed 0.5 in one
dialog and 0.35 in another is the same drift. Both outliers are corrected.

The scrim is pinned by a test that it differs between the two palettes, which is
what `Color::BLACK` could never satisfy. The relations between the three
constants are compile-time assertions rather than tests: clippy is right that a
runtime assertion over constants proves nothing, and a wrong value should fail
the build.

The background washes are left alone. They are the case `Palette::selection` is
actually about, they need opaque per-palette colours rather than a named alpha,
and that is the colour-roles work rather than this.
@youhide
youhide merged commit c66ca6a into main Aug 21, 2026
7 checks passed
@youhide
youhide deleted the t3-scrim-and-states branch August 21, 2026 02:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant