Skip to content

feat(client): OS-native window backdrop materials (UI/UX v3 P2c) - #75

Merged
mizu-jun merged 12 commits into
masterfrom
p2c-window-backdrop
Aug 28, 2026
Merged

feat(client): OS-native window backdrop materials (UI/UX v3 P2c)#75
mizu-jun merged 12 commits into
masterfrom
p2c-window-backdrop

Conversation

@mizu-jun

Copy link
Copy Markdown
Owner

Closes the last unchecked item of UI/UX v3 phase P2. window.backdrop selects the
OS-native window backdrop material, resolved per-OS and applied through Windows'
DwmSetWindowAttribute or macOS's NSVisualEffectView.

Config value Windows macOS Linux
auto (default) Mica Alt none none
mica Mica vibrancy none
mica-alt Mica Alt vibrancy none
acrylic Acrylic vibrancy none
none none none none

macOS collapses all three materials onto one vibrancy: AppKit has a single material
family, and mapping mica to "no backdrop" there would silently ignore what the user
asked for. Linux has no cross-compositor equivalent — in_app_blur_enabled (P2b) is
the in-app substitute.

Three defects this also fixes

The Windows material was mislabelled everywhere. apply_acrylic_blur passed 4
to DWMWA_SYSTEMBACKDROP_TYPE. 4 is DWMSBT_TABBEDWINDOW — Mica Alt;
DWMSBT_TRANSIENTWINDOW (Acrylic) is 3. The function name, its doc comment, the
crate CLAUDE.md and CHANGELOG.md:2898 all said Acrylic and all named the constant
DWMWCP_ACRYLIC, which belongs to the corner-preference enum. auto deliberately
still resolves to Mica Alt, so no existing Windows window changes appearance
— only
the naming was wrong.

A requested backdrop did not make the window transparent. Transparency keyed off
background_opacity < 1.0 alone, so backdrop = "acrylic" on an opaque-configured
window produced a surface the material could never show through.

Secondary OS windows got no backdrop at all. spawn_os_window never applied one,
so a second window never matched the first.

Also removed: window.macos_window_background_blur, which had no reader anywhere in
the workspace while CONFIGURATION.md and ARCHITECTURE.md both described it as
working. Unknown keys are ignored by the parser, so config files still carrying it
keep loading — a test pins that.

Design note: the resolver takes the OS as a parameter

WindowBackdrop::resolve(self, BackdropTarget) does not read cfg!, and
dwm_backdrop_value is a plain const fn compiled on every platform. Both are
deliberate: it means the Windows and macOS routing tables and every DWM constant are
asserted on the Linux runners that are the only machines running these tests. A wrong
constant fails on Linux rather than surviving to a Windows release.

New dependency

window-vibrancy 0.8 (Apache-2.0 OR MIT, matching this workspace), macOS only.
Windows keeps Nexterm's own DwmSetWindowAttribute call: the crate's Windows Acrylic
path falls back on Windows 10 to the undocumented SetWindowCompositionAttribute,
whose resize cost its own README documents — a poor trade for a terminal — and it
wants windows-sys 0.60 against this workspace's 0.59.

Because Cargo.lock records the union across all targets, windows-sys 0.60.2 and
the objc2 subtree do appear there and in pkg/flatpak/cargo-sources.json (12
packages, 156 lines) without ever being built on Linux. Adding the dependency also
re-resolved several existing crates' windows-sys edge onto 0.61.2, a version
already present in the lock; no new package version entered the tree.

Test plan

Machine-verified, all on Linux:

  • Resolver: 5 config values x 3 target OSes = 15 cells, plus separate pins that
    auto keeps its shipped behaviour on Windows (Mica Alt) and macOS (none)
  • Every DWM constant against the documented DWM_SYSTEMBACKDROP_TYPE enum
  • Serde: kebab-case round-trip, auto default, unknown value rejected, and an
    old config carrying macos_window_background_blur still loading
  • Settings cycler: closed loop in both directions, and the value reaching the
    TOML file through the real write-back path
  • All 6 new locale keys across all 8 languages (key-parity test)
  • cargo test --workspace (1,747 passing), cargo clippy -- -D warnings,
    cargo fmt --check

Not verified by anything — please read this before merging. No test executes
DwmSetWindowAttribute or apply_vibrancy; the Windows and macOS CI jobs prove the
code compiles and nothing more. Specifically unverified:

These join the on-device verification backlog in
docs/plans/ui-ux-modernization-v3.md rather than resolving any of it.

Generated with Claude Code
via Happy

mizu-jun and others added 12 commits August 28, 2026 07:30
Records the approved design for the last unchecked P2 item: a
`window.backdrop` config mapped to the native material on each OS.

Two findings from the pre-flight read are worth calling out, because both
contradict what the repo currently documents:

- `apply_acrylic_blur` passes `4` to `DWMWA_SYSTEMBACKDROP_TYPE`, which is
  `DWMSBT_TABBEDWINDOW` — Mica Alt, not Acrylic. The function name, its doc
  comment, the crate CLAUDE.md and CHANGELOG.md:2898 all say "Acrylic" and all
  name the constant `DWMWCP_ACRYLIC`, which belongs to the corner-preference
  enum. Only the plan got it right.
- Secondary OS windows from `spawn_os_window` never get a backdrop at all, and
  neither path treats a requested backdrop as a reason to create the window
  transparent.

`window-vibrancy` is vetted and adopted for macOS only: its Windows Acrylic
path falls back to the undocumented `SetWindowCompositionAttribute` on Windows
10, whose resize cost the crate's own README documents.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eight tasks across two PRs. Two of them are shaped by tests that already
exist: removing `macos_window_background_blur` breaks
`doc_matches_schema::complete_example_uses_only_real_config_keys` until
CONFIGURATION.md is updated, and adding a Window row breaks the
`window_row_labels().len() == WINDOW_ROW_COUNT` guard until the search
labels follow. Both failures are scheduled as steps rather than avoided.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The resolver takes the target OS as a parameter instead of reading cfg!,
which is what lets the Windows and macOS routing be asserted from the
Linux machines that are the only ones running these tests.

auto resolves to Mica Alt on Windows and to nothing on macOS: both are
what ships today, and a default that changed either would alter an
existing user's window on upgrade.
macos_window_background_blur has never had a reader anywhere in the
workspace, while CONFIGURATION.md and ARCHITECTURE.md both described it as
a working setting. It is removed rather than deprecated: the TOML parser
ignores unknown keys, so config files still carrying it keep loading, and a
test pins that.

Note on doc_matches_schema: `complete_example_uses_only_real_config_keys`
only compares top-level table/key names, so it did not fail on its own for
this nested `[window]` field (verified by running the suite before editing
the docs — all tests still passed). The drift was caught by manual review
instead. `removed_phantom_keys_do_not_return` now names
macos_window_background_blur so it cannot silently return.
background_opacity's documented default was wrong in the same table (1.0
against the schema's 0.95) and is corrected here, because the backdrop
caveat depends on it.
apply_acrylic_blur passed 4 to DWMWA_SYSTEMBACKDROP_TYPE. 4 is
DWMSBT_TABBEDWINDOW, i.e. Mica Alt; DWMSBT_TRANSIENTWINDOW (Acrylic) is 3.
The name, the doc comment, the crate CLAUDE.md and CHANGELOG.md:2898 all
said Acrylic, and all named the constant DWMWCP_ACRYLIC, which belongs to
the corner-preference enum.

The value mapping is a plain const fn compiled everywhere, so a wrong
constant fails on a Linux runner rather than only on Windows CI. The
failing HRESULT that older Windows returns is now logged instead of
discarded.
Two gaps close here. Requesting a backdrop now makes the window transparent
on its own: previously only background_opacity < 1.0 did, so
backdrop = "acrylic" on an opaque-configured window produced a surface the
material could never show through. And spawn_os_window applied no backdrop
at all, so a second OS window never matched the first.

When a backdrop is configured against an opaque terminal, the mismatch is
logged once at startup. Nothing is overridden: the configured opacity wins
and the user is told why they see no material.
window-vibrancy is adopted for macOS only. Its Windows Acrylic path falls
back on Windows 10 to the undocumented SetWindowCompositionAttribute, whose
resize cost its own README documents, and it wants windows-sys 0.60 against
this workspace's 0.59; Nexterm keeps its own DWM call there. Licensed
Apache-2.0 OR MIT, matching this workspace.

The material constant is an unmeasured initial recipe. Nobody on this
project can run macOS, so this arm is written blind and compiles on CI
without ever being looked at.
Follows the decorations cycler exactly: a five-value loop, a TOML key
distinct from the localised label, and a write-back through toml_edit that
preserves the rest of the file. The row itself lands in the next commit.
A Cycle row described once in settings_window.rs; the renderer, the
hit-test and the AccessKit tree pick it up from the descriptor, and the
sidebar search picks it up from window_row_labels. Restart-scoped, like the
decorations row beside it.

Mica and Acrylic stay untranslated in all eight locales: they are Microsoft
material names, not descriptions.

Also, folded into this final P2c-2 commit:
- Wire row 16 into window_field_increase/window_field_decrease
  (window_extra.rs), which the brief's file list omitted; without it,
  prev_window_backdrop stayed unreachable and clippy's dead-code lint
  caught it after the Task 7 allow(dead_code) attributes were removed.
- Remove the three temporary #[allow(dead_code)] attributes (and their
  explanatory comment) that Task 7 added to next_window_backdrop,
  prev_window_backdrop and window_backdrop_label.
- Fix docs/ARCHITECTURE.md's stale window.background_opacity default
  (1.0 -> 0.95, matching the schema).
- Name window-vibrancy itself in the P2c spec's "12 new lock entries"
  parenthetical, which enumerated only 11.
- Correct the plan's P2c line: it claimed "shipped via P2c-1/P2c-2" while
  neither has been merged (no PR opened on this branch); reworded to
  describe what's actually true.
The final whole-branch review found that adding window-vibrancy (macOS
only) re-resolved several existing crates' windows-sys dependency edge
from 0.59.0/0.48.0 to 0.61.2 (dirs-sys, errno, is-terminal, nu-ansi-term,
rustix, tempfile, and others), and iana-time-zone's windows-core edge to
0.62.2. Both versions were already present elsewhere in the lock file, so
no new package version entered the tree and nothing on Linux behaves
differently.

An SDD ledger entry had disputed this same observation during Task 4 and
called it false. That disproof used `grep -c '^-version'`, which matches
only a package's own top-level `version =` field, not a version string
quoted inside another package's `dependencies` array — the two checks
answer different questions, and the disproof could not have detected this
class of change either way. Corrected in both places it was recorded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@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 ffae207 into master Aug 28, 2026
12 checks passed
@mizu-jun
mizu-jun deleted the p2c-window-backdrop branch August 28, 2026 10:17
mizu-jun added a commit that referenced this pull request Aug 28, 2026
The entry was deliberately left unchecked while the branch was unmerged —
every sibling line in this document cites an actual merged PR number, so
claiming "shipped" before one existed would have been false. #75 is merged,
so it is now true.

Also records the two latent defects P2c closed alongside its own feature
(a requested backdrop never made the window transparent; secondary OS
windows never received one), since neither is obvious from the phase's
title.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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