Skip to content

refactor(client): put the modal dialog buttons on tokens, readably (UI/UX v3 G11 follow-up) - #71

Merged
mizu-jun merged 1 commit into
masterfrom
refactor/g11-followup-dialog-buttons
Aug 21, 2026
Merged

refactor(client): put the modal dialog buttons on tokens, readably (UI/UX v3 G11 follow-up)#71
mizu-jun merged 1 commit into
masterfrom
refactor/g11-followup-dialog-buttons

Conversation

@mizu-jun

Copy link
Copy Markdown
Owner

What

Third and last code part of the #62 follow-up (after #69 and #70): the close-window dialog's Kill/Cancel buttons, and the consent dialog's selected-button fill.

Site Was Now
Kill at rest [0.75, 0.25, 0.25] danger_fill(tokens, 0.55)
Kill selected [0.95, 0.40, 0.40] danger_fill(tokens, 0.85)
Cancel selected [0.95, 0.85, 0.40] caution_fill(tokens, 0.85)
Consent selected raw semantic_warning caution_fill(tokens, 0.85)
Selected-button label (both dialogs) [0.10, 0.10, 0.10] fixed on_surface_text(fill)

Why Kill does not reuse #70's helper

danger_button_colors answers "is this button focused" — it steps from a barely-tinted rest state to a mid blend, which is right for a settings delete button that only turns red once you land on it. Kill has to read as the destructive choice before it is selected, because it sits next to Cancel. So it steps from that same mid blend to a strong one. Sharing the blend and not the focused/unfocused rule is what keeps both readings intact.

What the measurement changed

The labels are the substance here. A fixed near-black label is wrong on any dark fill, so labels now come from on_surface_text (added in #69). Measuring that across all nine built-in schemes turned up something no fixed blend strength can fix:

Fill Scheme Best either extreme manages
semantic_error at 0.85 Nord 4.42:1
semantic_warning used raw Solarized 4.37:1

Both are middling luminances — neither a near-black nor a near-white label has anything to contrast with. (This is also why caution_fill blends instead of passing the token straight through.)

semantic_fill therefore walks the blend back toward surface_1 until the label clears MIN_TEXT_CONTRAST — the same shape as row::ensure_readable. It terminates because surface_1 derives from the scheme background, the end of the range a label can always be read against. The trade: some schemes get a slightly quieter fill than requested. An unreadable label on a destructive button is worse.

The consent dialog's 3 px top stripe and its title keep the raw warning hue — a line and text, not a fill.

Test plan

  • cargo fmt --check
  • cargo clippy --all-targets -- -D warnings — clean through every crate
  • cargo test --workspace — all pass, 0 failures (909 in this crate)
  • Re-verified after rebasing onto merged master
  • Two new tests. One pins every fill/label pair the dialogs paint, across all nine built-in schemes, at 4.5:1 — this is the test that forced caution_fill to exist, and it fails on Nord if the adaptive step is removed (verified by removing it). The other pins that a stronger blend always reads as redder, since that ordering is what carries both "dangerous" and "selected"
  • On-device visual check — not run. Joins the accepted-unverified backlog in docs/plans/ui-ux-modernization-v3.md. Worth a look: whether the adaptive step-back leaves Kill visibly red enough on Nord, and the Cancel-selected fill now that it is blended rather than a flat yellow

G11 follow-up after this PR

Everything the plan listed is now migrated except two items, both deliberate: picker.rs's purple/green macro & SSH branding (its comments call it intentional — needs a product decision, not a mechanical migration) and color_util::resolve_color's no-palette fallbacks (unreachable outside tests, explained in #69). A plan update recording this follows separately.

🤖 Generated with Claude Code

…I/UX v3 G11 follow-up)

Third and last code half of the #62 follow-up: the close-window dialog's
Kill/Cancel buttons, and the consent dialog's selected-button fill.

The literals were `[0.75, 0.25, 0.25]` / `[0.95, 0.40, 0.40]` for Kill at
rest and selected, `[0.95, 0.85, 0.40]` for a selected Cancel, and
`[0.10, 0.10, 0.10]` for the label on any selected button (in two
dialogs). All now derive from `semantic_error` / `semantic_warning` via
two new helpers, `util::danger_fill` and `util::caution_fill`.

Kill deliberately does *not* reuse #70's `danger_button_colors`. That
helper answers "is this button focused", stepping from a barely-tinted
rest state to a mid blend. Kill has to read as the destructive choice
*before* it is selected — it sits next to Cancel — so it steps from the
mid blend to a strong one instead. Sharing the blend and not the
focused/unfocused rule is what keeps both readings intact.

The labels are the substance of this change. Selected-button labels were
a fixed near-black, which is wrong on any dark fill; they now come from
`on_surface_text`, chosen against the fill. Measuring that across the
nine built-in schemes turned up something a fixed blend strength cannot
fix: at 0.85 the error hue lands at a middling luminance on Nord
(4.42:1) and the warning hue does the same on Solarized (4.37:1) —
luminances where *neither* extreme has anything to contrast with. Used
raw, `semantic_warning` fails the same way, which is why `caution_fill`
blends rather than passing the token through.

`semantic_fill` therefore walks the blend back toward `surface_1` until
the label clears `MIN_TEXT_CONTRAST`, the same shape as
`row::ensure_readable`. It terminates because `surface_1` derives from
the scheme background — the end of the range a label can always be read
against. Some schemes get a slightly quieter fill than asked for; an
unreadable label on a destructive button is the worse trade.

The consent dialog's 3 px top stripe and its title keep the raw warning
hue: a line and text, not a fill.

Two new tests. One pins every fill/label pair the dialogs paint across
all nine schemes at 4.5:1 — it is the test that forced `caution_fill` to
exist, and it fails on `Nord` if the adaptive step is removed. The other
pins that a stronger blend always reads as redder, since that ordering
is what carries "dangerous" and "selected".

On-device verification: not run. Joins the accepted-unverified backlog.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
@github-actions

Copy link
Copy Markdown

Coverage report


Generated by cargo llvm-cov (workspace minus nexterm-client-gpu and nexterm-i18n).

@mizu-jun
mizu-jun merged commit fe2db95 into master Aug 21, 2026
12 checks passed
@mizu-jun
mizu-jun deleted the refactor/g11-followup-dialog-buttons branch August 21, 2026 13:40
mizu-jun added a commit that referenced this pull request Aug 21, 2026
)

The follow-up entry has been an open checkbox holding a list of sites
since #62. It now records what was actually done in #69 / #70 / #71,
split by surface, plus the two sites that stay out and why they are
decisions rather than leftovers: `picker.rs`'s per-feature brand hues
need a product call, and `resolve_color`'s no-palette fallbacks sit on a
branch `render_frame` cannot reach outside tests.

Three findings are worth keeping in the plan rather than only in commit
messages, because each contradicts what the entry previously assumed:

- The delete-dialog reds had drifted further than recorded — not only the
  focused fill but the resting treatment and the label rule.
- The scrim was never "one literal in four files". It was an asymmetry:
  five modal surfaces, only one of them scheme-derived, so a light scheme
  put a black veil behind a light panel.
- No fixed blend strength can carry a semantic fill across all nine
  schemes. Nord (error, 4.42:1) and Solarized (warning used raw, 4.37:1)
  land at luminances where neither a near-black nor a near-white label
  has anything to contrast with, which is why `semantic_fill` steps the
  blend back until the label clears the floor.

The on-device backlog gains #69 / #70 / #71, with #70 flagged as the
highest-value item in the list: the scrim is the one place this work
changed what an existing surface *looks like* rather than where its
value comes from, and that was reasoned about, never seen. The same
section now also states what the colour work *did* get machine-verified,
so the distinction between measured readability and unverified
appearance stays explicit.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>
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