Skip to content

feat(window): working background opacity and blur on macOS, Windows and Linux - #36580

Merged
nwparker merged 3 commits into
mainfrom
nwparker/theme-window-material
Nov 1, 2026
Merged

nwparker merged 3 commits into
mainfrom
nwparker/theme-window-material

Conversation

@nwparker

@nwparker nwparker commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 5 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​138 $\color{#cf222e}{\Huge{\mathbf{−}}}$​58 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​80
Prod 50 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​641 $\color{#cf222e}{\Huge{\mathbf{−}}}$​202 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​439

Perf

TL;DR:

  • The default (Opaque) costs nothing: it's byte-identical to origin/main on every platform and metric measured.
  • When a see-through material is on, nearly all the extra cost is paid by the OS compositor (macOS WindowServer, Windows DWM, the Linux compositor), not by Orca. Orca's renderer work per frame is unchanged apart from about 0.13 ms/frame of extra compositing.
  • On macOS, pick Blurred over Translucent. Blurred is near-free at idle. Translucent makes WindowServer re-blend the whole window whenever anything behind it changes.

Where the cost comes from (plain language)

  • Opaque window: the OS can treat the window as a solid rectangle. It only redraws the parts that changed and skips everything hidden behind it.
  • Translucent window: every pixel has to be mixed with whatever is behind it, on every frame where either changes. So the cost scales with how much changes behind the window (video, another live terminal, animations) and with how fast Orca redraws, for example during heavy output.
  • Blurred window: macOS vibrancy and Windows acrylic sample the backdrop through a system effect that the OS optimizes and updates lazily, so it is much cheaper than raw alpha on macOS.
  • Orca's own work is the same in every mode. It still draws the terminal once per frame. With a material on, the terminal background is painted once instead of 3–4 stacked layers, plus one framebuffer clear per WebGL frame (the [Bug]: Terminal background flickers on every redraw when background transparency is enabled #6491 fix).

Which setting to choose

You want Choose Cost
Maximum battery / no effects, or a remote/VM/RDP session Opaque (default), or turn on Reduce Visual Effects none
See-through terminal on macOS Blurred ~+10 mW idle; ~+10 pts GPU busy under full-speed output
Sharp see-through on macOS (Ghostty-style) Translucent 50–890 mW idle depending on what is behind the window; GPU ~83% vs 47% under full-speed output
See-through on Windows 11 22H2+ Blurred (acrylic) about +1 pt DWM GPU idle; parity under output
See-through on Linux Translucent (your compositor's blur rules apply) GNOME/Mutter: +15% of a core under heavy output, 0 idle. Hyprland: +4 pts GPU busy under output, 0 idle

Window material + theme image (macOS, sudo powermetrics)

On a vibrancy window, the hidden material's cost shows up only under full-speed output. Three interleaved rounds of Blurred + Dusk test image vs Opaque + the same image:

  • GPU residency: 62.6% vs 56.1%.
  • WindowServer CPU: 160 vs 149 ms/s.
  • Idle: identical (about 18% residency, about 110 mW, dominated by other load on the shared machine).

With this change (vibrancy dropped while the image is shown), Blurred + image measured 55.9% and 150 ms/s, the same as Opaque + image. That's about 6.5 points of GPU residency and about 7% WindowServer CPU saved under heavy output, and nothing at idle. Restore was verified on screen: removing the image brings the blurred desktop back.

What was scanned (perf-scan over the full diff vs origin/main)

Paths reviewed, with their cost:

  • xterm WebGL patch: one clear + blendFuncSeparate per rendered frame, only when allowTransparency.
  • applyTerminalAppearance: one extra theme composition per appearance apply, not per frame. It is value-gated, so a no-op apply doesn't rebuild the palette.
  • main.css rules:
    • :root[data-window-material] and :root[data-reduce-effects] selectors match nothing unless the attribute is set.
    • The universal backdrop-filter: none rule applies only under Reduce Visual Effects.
  • useMediaQuery / useReduceVisualEffects: one matchMedia listener per window (main, space, popout).
  • activeWindowMaterial(): cached once per renderer.
  • TabGroupPanel / TabGroupSplitLayout: constant attribute reads.
  • Main process: resolveWindowMaterial with one os.release() and one nativeTheme read at window creation.
  • Secondary windows stay opaque, so the per-window compositor cost doesn't multiply.

Findings: none. I found no repeated, amplified or abandoned work. No per-frame JS, no new timers or polls, no growth with panes or terminals, and nothing runs when the feature is off. Off-path proof: with the material off the window gets no new option and no new argument, and every new CSS rule is attribute-gated.

macOS (Apple M4, macOS 26.6.2): sudo powermetrics, visible 1300×820 window in front of a test pattern

Median of 3 interleaved 20 s rounds, dark theme. Stream load is yes '<80-char line>' into the terminal at full speed.

Config Idle GPU power Idle GPU residency Stream GPU power Stream GPU residency Stream WindowServer CPU (ms/s) Stream Orca CPU (ms/s)
origin/main (opacity 0.7 + old Window Blur → opaque window) 36 mW 6.5% 536 mW 47.2% 137 480
Opaque (opacity 1) 39 mW 6.9% 545 mW 46.6% 135 481
Blurred (0.7) 49 mW 8.4% 805 mW (noisy: 135–946) 57.1% 157 503
Translucent (0.7) 313 mW (50–890) 35.6% 655 mW 83.3% 73 507
  • Translucent's idle cost depends on how much changes behind the window, and it is 0 when the window is hidden.
  • A separate ioreg AGX-utilization run (5–7 runs per config) agrees: idle GPU utilization was 5.8% Opaque, 10.9% Blurred and 40.4% Translucent.
  • Raw samples are in the powermetrics comment.

Linux

Hyprland 0.56 (omarchy), Intel UHD 770, Wayland, visible tiled window. GPU busy is computed from i915 RC6 residency. Hyprland's blur was enabled at runtime only for the "+ blur" rows. The machine already had a ~42% GPU-busy baseline from other clients. Two rounds per config, 20 s each:

Config Idle Hyprland CPU Idle GPU busy Stream Hyprland CPU Stream GPU busy Stream Orca CPU
Opaque 4.0% 42.3% 5.6% 48.4% 200%
Translucent 4.0% 42.4% 5.5% 52.5% 194%
Translucent + Hyprland blur 4.1% 44.2% 5.6% 51.5% 196%

GNOME/Mutter X11 (openclaw, phase 2): compositor CPU idle 0.2–0.3% for both Opaque and Translucent. Under heavy output it is 22% → 37% (+15% of a core) for Translucent.

Windows 11 25H2 (awin, phase 2, over RDP)

Opaque vs acrylic:

  • Idle: Orca CPU 1.0% vs 1.4%; DWM GPU 0.0% vs 1.0%.
  • Under output: Orca CPU 162% vs 171%; DWM GPU 4.7% vs 5.9%.

These numbers are thin. Over RDP, DWM draws acrylic as its solid fallback tint, not live blur. A console-session measurement on Windows is still open.

Renderer-side cost (Orca's own work)

CDP trace on omarchy: a visible window, a dev build, and the same terminal workload. The trace categories were devtools.timeline, devtools.timeline.frame, viz, gpu and cc. Performance.getMetrics covered layout, style, script and task. Each row is the mean of 2 rounds over 10 s of streaming, in ms of work per second (60 frames/s in every config).

Config Paint Composite (layerize/commit/prepaint) GPU tasks Layout Style Script Total renderer tasks JS heap
origin/main, opacity 1 54.0 56.8 81.3 52.2 52.3 411 782 119–133 MB
origin/main, opacity 0.7 53.2 56.1 79.3 50.8 51.1 399 768 119–124 MB
PR Opaque, opacity 1 53.5 57.0 77.1 51.8 52.1 372 742 118–131 MB
PR, opacity 0.7, material off 54.0 57.6 80.3 53.5 54.2 376 751 138–139 MB
PR, material on (opacity 0.7) 52.6 64.4 79.9 53.0 53.5 360 739 138–139 MB
  • Idle: every config is identical at 2 frames/s, about 18 ms/s of renderer tasks, about 0.6 ms/s paint and about 1.5 ms/s composite.
  • With the material on, compositing goes up by about 7.5 ms/s (about 0.13 ms per frame), because one extra translucent layer is blended. Total renderer work is unchanged.
  • Renderer-side code is identical on macOS and Windows: Blurred and Translucent share the same DOM, CSS and xterm path.
  • Memory: JS heap stays within run-to-run noise. Process-tree PSS is 4.3–5.1 GB because the dev toolchain (Vite, electron-vite) dominates it. It varied by ±0.4 GB between runs with no pattern by config, so I don't claim a memory delta. The feature adds no retained objects: it is one cached string plus attribute-gated CSS.

Limitations:

  • These are dev builds.
  • The macOS power numbers come from a shared machine with other agents running. That's why I used 3 interleaved rounds and medians, and kept the noisy ranges.
  • There is no Windows console-session measurement.

Summary

Background Opacity and Window Blur did nothing useful on any platform. This replaces the old macOS-skipping, Windows-only blur switch with one window material model — Opaque / Translucent / Blurred — that actually shows the desktop through translucent terminals on macOS, Windows and Linux. It also adds Reduce Visual Effects.

Root causes (one fix each)

  1. The window was always opaque. createMainWindow always set an opaque backgroundColor; blur only mapped to Windows acrylic, and macOS/Linux had nothing behind the page.
  2. html/body painted an opaque --background over whatever material the window had.
  3. The terminal background was painted 3–4 times per pane — split root, .xterm, .xterm-scrollable-element and the WebGL canvas viewport rect. So 0.82 rendered as 1 − 0.18³ ≈ 0.994 everywhere, including Windows.
  4. Transparent WebGL frames accumulated alpha ([Bug]: Terminal background flickers on every redraw when background transparency is enabled #6491). Each redraw blended translucent backgrounds over the previous frame without clearing, and SRC_ALPHA on the alpha channel turned opacity a into a².

What changed

  • src/shared/window-material.ts is the single model. It covers the stored preference (with a migration from the legacy windowBackgroundBlur), per-host capability, and resolution. The material is only engaged when something actually shows through: Background Opacity < 1, Reduce Visual Effects off, and the OS Reduce Transparency setting off. An occluded vibrancy or alpha window is pure WindowServer cost ([Bug]: Window Blur causes sustained high GPU power and heat on macOS #8482), so that case now resolves to Opaque.
  • window-material-options.ts sets the creation-time BrowserWindow options per platform:
    • macOS Blurred: vibrancy: 'under-window', visualEffectState: 'active' and a clear backgroundColor. It deliberately sets no transparent: true: that flag suppressed the effect view ([Bug]: "Background Opacity" and "Window Blur" have no visible effect on macOS #8797) and forced full-window alpha compositing ([Bug]: Window Blur causes sustained high GPU power and heat on macOS #8482). active keeps the glass when Orca is unfocused instead of turning flat grey.
    • macOS Translucent: transparent: true with a clear background, for Ghostty-style unblurred see-through.
    • Windows Blurred: backgroundMaterial: 'acrylic' with a clear background. It is offered only on Windows 11 22H2+ (build 22621), where DWM system backdrops exist. Older builds keep Opaque instead of painting black.
    • Linux Translucent: transparent: true with a clear background. KWin, Mutter, Hyprland and picom composite it and apply their own blur rules. A stored "Blurred" maps to this.
  • The renderer learns the material the window was created with through a --orca-window-material= argument, which preload exposes as platform.get().windowMaterial. It is never inferred from settings, so toggling before restart can't paint a clear page on an opaque window. When the material is off no argument is passed, so the window is byte-identical to today.
  • Exactly one translucent paint. With a material active, :root[data-window-material] body is transparent and the pane split root keeps the rgba(theme, opacity) paint. xterm gets the same theme background with alpha 0, which covers the DOM viewport, the scrollable element and the WebGL viewport rect in one place, with no !important overrides. RGB is preserved, so minimum-contrast and inverse video still use the real background colour. With the material off, nothing changes: same xterm theme, same layers, same cost.
  • The sidebar/workspace seam's translucent 1px border used to blend over the body. It now gets that base back, so no desktop slit shows (measured: alpha 1 at the seam).
  • WebGL flicker fix ([Bug]: Terminal background flickers on every redraw when background transparency is enabled #6491) lives in config/patches/xterm-src/@xterm__addon-webgl@0.20.0-beta.300.src.patch, built on fix(terminal): prevent transparent WebGL flicker (#6491) #6597. When allowTransparency is set, each frame uses separate alpha blending (SRC_ALPHA, ONE_MINUS_SRC_ALPHA, ONE, ONE_MINUS_SRC_ALPHA) and clears the framebuffer before drawing. Opaque terminals skip the block. The bundles and lockfile hash were regenerated with config/scripts/regenerate-xterm-patches.mjs --write.
  • Reduce Visual Effects (Settings → Appearance → Interface → Advanced) sets one data-reduce-effects switch. The OS prefers-reduced-transparency sets the same switch. It does four things:
    • drops the 2px modal workspace blur (filter: blur, the composer lag in [Feature]: option to disable modal backdrop blur #20936);
    • removes every CSS backdrop-filter;
    • turns the translucent menu/dialog surfaces solid (--floating-surface, --dialog-surface, and the existing reduced-transparency menu fallbacks, now a reduce-effects: variant);
    • turns the window material off after a restart.
      It applies to the main window, the workspace space window and the dashboard popout. usePrefersReducedMotion now shares a small useMediaQuery hook instead of a copy.
  • Settings: the Window Blur switch becomes a Window Material select, filtered per host. The existing restart banner is reused and now compares the resolved material with the running window, so opacity or Reduce Visual Effects changes also prompt correctly. Hints explain why the material is off (needs opacity < 1, reduced effects, unsupported Windows build). The control is hidden in the paired web client. Ghostty import maps background-blur (bool / radius / macos-glass-*) and the legacy background-blur-radius to Blurred or Translucent.
  • Migration / back-compat:
    • windowMaterial is unset for existing profiles, so windowBackgroundBlur: true reads as Blurred.
    • Every write of windowMaterial also mirrors windowBackgroundBlur at the settings-store boundary (from the select or a Ghostty import), so a rollback to an older Orca keeps the closest behaviour.
    • Older Orca ignores the new keys. No IPC, RPC or mobile protocol changes.
  • Secondary windows (workspace space window, dashboard popout) stay opaque on purpose. They are transient utility windows. A material there would multiply the per-window WindowServer cost from [Bug]: Window Blur causes sustained high GPU power and heat on macOS #8482 with nothing translucent to reveal (popout previews already paint their own terminal backgrounds). Reduce Visual Effects does apply to them.

Closes #8797
Closes #36196
Closes #6491
Closes #20936
Refs #19301 (window/terminal transparency part; app-chrome transparency is not in scope)
Refs #23305 (opacity part only; no bindable toggle command added)
Refs #24751 (not addressed: per-panel opacity for the floating workspace is a separate in-window setting)

Supersedes / builds on #2791 by @edwintuan, #3972 by @MuathJ, #12470 by @nwparker, #24359 by @pawel-kotowski, #5425 by @TomBonness, #6597 by @nwparker, #36197 by @AdolfoCarneiro and #10349 by @innocarpe. #24359's finding that vibrancy works over a clear background without transparent: true is the macOS design here. #36197's Linux transparent window and single-paint analysis is the Linux design. #6597 is the WebGL fix, ported to the new source-patch pipeline.

Visual proof

CDP screenshots carry the renderer's alpha but not the desktop behind the window. A background-launched test window is never shown, so the native vibrancy/acrylic pass can't be captured without stealing focus. Each "after" below is the real CDP PNG alpha composited over a synthetic wallpaper; the after images use a pre-blurred copy to stand in for vibrancy. The "before" images are origin/main with the same settings (Background Opacity 0.82, Window Blur on).

Dark (before → after)
dark

Light (before → after)
light

Pixel alpha (CDP, opacity 0.82):

Region origin/main this PR (material on)
Terminal background opaque rgb(46,50,60) (≈0.994 stacked) rgba(40,44,52,0.82) exactly
Sidebars, tab bar, status bar, file panel opaque opaque (unchanged)
Sidebar seam opaque opaque (fixed slit)
Settings / Tasks / Automations / Skills / landing page opaque opaque: 0% of pixels below 50% alpha

Layers painting the terminal background, from DOM inspection:

  • origin/main: .xterm-scrollable-element, .xterm, split root (each 0.82), plus body and the WebGL rect.
  • This PR: split root only (0.82). The xterm layers report rgba(…, 0).

WebGL flicker (#6491): an empty cell sampled after six successive redraws, Background Opacity 0.3, material off, WebGL renderer:

redraws 1–6
origin/main rgb(38,42,51) — canvas alpha has already accumulated to ~1 (the configured 0.3 is lost)
this PR rgb(32,35,43) — stable, matching the analytic composite of the configured alpha

Reduce Visual Effects, New Workspace composer open, dark and light (default → reduced: no workspace blur):
reduce dark
reduce light

Setting + reused restart banner
setting

Merge-risk assessment

Low (was Medium-low, before that Medium). All three desktop OSes are now verified on real hardware with visible windows. Evidence is in the phase-2 comment and the phase-3 comment:

  • Linux, now verified on a real desktop (GNOME/Mutter X11):

    • the desktop shows through translucent terminals in dark and light;
    • no black window, and resize works;
    • modals keep an opaque base;
    • Reduce Visual Effects forces Opaque;
    • the legacy switch is not migrated, as designed.
  • Windows 11 25H2, now verified:

    • acrylic is engaged on 22H2+;
    • the legacy switch migrates to Blurred;
    • no black window;
    • CPU/GPU is at parity with Opaque.
      RDP shows the acrylic fallback tint, not live blur.
  • macOS, now verified on a visible window (M4, macOS 26.6.2, real screencapture over a test pattern):

    • Translucent shows the desktop sharply and Blurred shows it blurred, in dark and light. Chrome stays opaque.
    • The native shadow is kept for all materials.
    • Modals keep an opaque base, and resize works.
    • Reduce Visual Effects and macOS Reduce transparency both force Opaque.
    • Cost was measured against origin/main with sudo powermetrics, plus a separate ioreg run:
      • Opaque matches main.
      • Blurred is at noise level idle (+10 mW). Under full-speed output it adds about +10 points of GPU residency and +16% WindowServer CPU.
      • Translucent costs 50–890 mW idle, depending on what changes behind the window, and about 83% vs 47% GPU residency under output. That's WindowServer compositing the alpha window, and it drops to 0 when the window is hidden.
  • Hyprland, now verified (omarchy, Hyprland 0.56, Wayland):

    • Translucent shows the wallpaper sharp, and with Hyprland's blur enabled it is blurred.
    • Reduce Visual Effects forces Opaque.
    • Compositor cost is in the Perf section.
  • Perf scan: no findings. With the feature off nothing runs, and renderer work is unchanged with it on. See the Perf section.

  • Remaining unverified: a Windows console session, since the Windows numbers were taken over RDP.

  • Off path (the default): unchanged. No window option, no argument, the same layers.

  • Rebased onto feat(themes): background images for app themes #36589 (theme background images). feat(themes): background images for app themes #36589 landed the shared "translucent terminal over a backdrop" plumbing:

    • the single-paint xterm layering;
    • the WebGL clear/blend patch and its lockfile hash;
    • the seam, gutter and tab-body CSS;
    • use-media-query.

    This PR now plugs the window material into that plumbing:

    • window-backdrop.ts is active for a window material or a theme image;
    • the shared CSS rules use :root:is([data-window-material], [data-theme-background]);
    • the Reduce Visual Effects setting now also drops the theme image, which on main only the OS preference did.

    Because the xterm patch is now identical to main's, this PR no longer changes config/patches or pnpm-lock.yaml.

  • Shared hot files: main.css additive rules; global-settings-types.ts (one line moved to GlobalSettingsBase).

Negative user-facing tradeoffs

  • People who turned on Window Blur long ago will now actually get it, if their Background Opacity is < 1. On macOS that brings back some WindowServer blur cost while the window is visible. With opacity 1 they now get a plain opaque window. On Windows that removes the invisible acrylic they had.

  • macOS Translucent uses transparent: true, which is the expensive choice on macOS. WindowServer alpha-composites the window against whatever is behind it. Measured on an M4 while visible:

    • 50–890 mW of GPU power at idle (median 313 mW, vs 39 mW for Opaque), driven by content changing behind the window;
    • 83% vs 47% GPU residency under full-speed output.

    It costs nothing while hidden. The native shadow is kept. Blurred is the cheaper see-through option on macOS.

  • macOS Blurred costs almost nothing idle (+10 mW). Under sustained full-speed output it costs about +10 points of GPU residency and +16% WindowServer CPU vs Opaque. At Background Opacity 0.7 the vibrancy effect is subtle, because the under-window material is fairly dense.

  • On Windows 10 and 11 before 22H2 the setting is disabled ("Requires Windows 11 version 22H2 or later").

  • Linux without a compositor (bare X11) shows black where the desktop would show. The setting is opt-in and the hint says the compositor decides.

  • Unfocused split groups are not dimmed while a window material is active (dimming via group opacity would make chrome see-through).

  • macOS Blurred uses visualEffectState: 'active', so the blur stays live while Orca is unfocused-but-visible (avoids a flat grey fill; costs WindowServer work in that state).

  • Linux users who had the old Window Blur switch on are not migrated; they choose Translucent explicitly.

  • TUIs that set the background with OSC 11 paint an opaque background over the material (same as today's translucent-terminal behaviour), and that colour persists until the theme is reapplied.

  • Reduce Visual Effects makes menus and dialogs solid and removes the frosted look. When it changes the window material, that part applies after a restart.

  • The "Window Blur" switch is gone. It is replaced by the Window Material select in the same place, and its search keywords still match.

  • Only terminals become translucent. Sidebars, tab bar, editors and pages stay opaque; app-wide glass (feat(theme): app-wide window glass with vibrancy/acrylic #3972, feat(window): make macOS window blur a real glass effect, with opt-in Interface Glass #24359's Interface Glass) is not included.

  • Linux translucency costs compositor CPU under heavy output. On GNOME/Mutter X11, sustained full-screen output costs about +15% of a core in the compositor (22% → 37%), with no idle cost. This is inherent to ARGB windows and applies only when Translucent is chosen.

  • Over Remote Desktop, Windows acrylic shows as its solid fallback tint, not a live blur.

  • A window material and a theme image together: the image wins. The body keeps its opaque base, so the image's letterbox and veil never expose the desktop.

    • macOS Blurred: the vibrancy is now dropped at runtime while the image is shown (setVibrancy(null)) and restored when the image goes away.
    • macOS Translucent (transparent: true, which can't change at runtime): it keeps paying alpha compositing under an image theme.
    • Windows acrylic: left on. Toggling the backdrop at runtime on our frameless window changes DWM frame styles, and we couldn't verify that on a console session.

Review

Two independent Opus reviews plus Codex (gpt-6.1-sol, xhigh). Fixed:

  • Windows < 11 22H2 would paint black → material gated on build 22621 (main os.release(), renderer osRelease).
  • Rollback read a stale legacy key → windowBackgroundBlur is mirrored on every windowMaterial write at the store boundary. A Codex review of the rebase found that the Ghostty import skipped it before.
  • Legacy migration silently enabling a transparent window on Linux → not promoted on Linux.
  • Translucent → acrylic on Windows → now resolves to Opaque.
  • Desktop slits at split gutters, non-terminal tab bodies, unfocused-split opacity and the modal blur fringe → opaque bases as above, re-measured (0% of pixels below 50% alpha outside terminals, incl. a split with an editor and the composer open).
  • Renderer helper renamed window-material-runtime.ts (no two window-material.ts).

Not changed, with reasoning: OSC 11 opaque backgrounds (same as today's translucent mode; preserving TUI colour mutations is intentional), renderer vs main reading reduced transparency through Chromium's media query vs nativeTheme (same OS source), Plain SSH panel (sits in an opaque card, unaffected), visualEffectState: 'active' (documented tradeoff).

Validation

  • pnpm tc (all projects) passes.
  • oxlint and oxfmt pass on the changed files.
  • Localization: pnpm sync:localization-catalog, pnpm sync:localization-runtime-catalog and pnpm verify:localization-catalogs all pass.
  • pnpm test run in a non-nested worktree:
    • window-material.test.ts (new)
    • createMainWindow*.test.ts (13 files)
    • ghostty/mapper.test.ts
    • terminal-appearance.test.ts, terminal-view-attributes-publisher.test.ts
    • pane-lifecycle.test.ts
    • regenerate-xterm-patches.test.mjs
    • use-document-appearance.test.tsx, AppearancePane.test.tsx
    • the settings TerminalPane / Ghostty tests
    • the native-chat reduced-motion consumers
    • SidebarFeedbackDialog.test.tsx
  • xterm patches regenerated with node config/scripts/regenerate-xterm-patches.mjs --write, and pnpm install --frozen-lockfile re-applied them.
  • Exercised in the running app (Electron dev build, ORCA_BACKGROUND_LAUNCH=1, CDP port 9317, throwaway profiles), on a folder workspace, both origin/main and this branch:
    • restarted with each material setting;
    • pixel-sampled the terminal, chrome, seam and five pages;
    • checked the computed backgrounds of each terminal layer;
    • redraw flicker sampling;
    • New Workspace composer with and without Reduce Visual Effects (workspace filter goes from blur(2px) to none);
    • the settings search for Window Material and Reduce Visual Effects, including the restart banner.
  • Still needed:
    • Windows 11 22H2+ acrylic and a Windows 10 fallback on real hardware;
    • Linux KDE/GNOME/Hyprland (Wayland and X11) and bare X11;
    • a visible-window macOS A/B for vibrancy versus opaque (done in phase 3 with powermetrics);
    • Hyprland (done in phase 3);
  • Phase 2, real hosts:
    • Fresh clone, built and run visibly on openclaw (GNOME X11), awin (Windows 11 25H2) and m4air (macOS 26), using OS screenshots and pixel/DOM checks.
    • Opaque, Translucent/Blurred and Reduce Visual Effects each exercised, plus a dialog and resize.
    • Old Window Blur migration checked on all three OSes.
    • GPU-acceleration toggle leak count.
    • Perf sampled (top; PowerShell GPU Engine counters).
    • Hyprland (omarchy) was blocked by a locked session in phases 2 and 3; macOS was done in phase 3 (below); win-lowspec is also 25H2 and its native-module rebuild failed during install, so it was not run.
  • Phase 3, macOS visible window (m4air):
    • Fresh clones of this branch and origin/main, run as dev builds with their own profiles and ports, in front of a colourful test pattern.
    • Opaque, Translucent and Blurred captured in dark and light.
    • Shadow alpha measured from window captures.
    • New Workspace modal, a resize, Reduce Visual Effects, and macOS Reduce transparency (toggled and restored) each checked.
    • GPU/WindowServer/Orca cost sampled idle and under yes, A/B against origin/main.
  • Phase 3, Hyprland (omarchy):
    • Fresh clone, launched with --ozone-platform=wayland and its own profile.
    • Opaque, Translucent (with and without Hyprland blur, which was turned on at runtime and restored afterwards), light Translucent and Reduce Visual Effects each checked with grim.
    • Hyprland CPU and i915 RC6 GPU busy measured idle and under yes.
  • Perf scan and renderer trace:
    • perf-scan over the full diff.
    • CDP trace (paint, composite, GPU, layout, style, script, JS heap) on visible windows, origin/main vs this branch, 2 rounds each.
  • Rebase onto feat(themes): background images for app themes #36589 (head 5b71d48c9bf):
    • pnpm tc passes; oxlint and oxfmt are clean; verify:localization-catalogs passes.
    • Focused tests pass: window-material, createMainWindow (12 files), Ghostty mapper, terminal-appearance, use-document-appearance, AppearancePane, the settings components, TabGroupPanel/SplitLayout and the plugin image protocol.
    • A new store-boundary test, settings-update-window-material.test.ts, covers the legacy-switch mirror.
    • Background-launched dev app checks (CDP probes and screenshot) with a local test theme plugin that has a background image:
      • Blurred alone: material blur, body transparent, xterm background alpha 0.
      • Blurred + theme image: the image paints over an opaque base.
      • Theme image only: the image shows, with a single translucent terminal paint.
      • Opaque (opacity 1): no image, opaque terminal.
      • Reduce Visual Effects: image dropped, data-reduce-effects set.
    • Delta reviewed by an Opus sub-agent and Codex.
  • Vibrancy suppression under a theme image (af51398cdaa):
    • New IPC app:setWindowMaterialSuppressed. It only accepts a trusted UI renderer and a boolean, and only acts on a window created with macOS blur.
    • The renderer sends it when applyThemeBackground toggles. It re-sends after a reload, retries after a failed call, and feature-detects the method for mixed versions.
    • Reviewed by an Opus sub-agent and Codex; all their findings are addressed.
    • Checks: pnpm tc, oxlint, oxfmt, src/main/window + src/main/ipc/app.test.ts (801 tests) and the src/renderer/src/lib suites.
    • Measured on m4air as above.

@coderabbitai

coderabbitai Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Important

Review skipped

We couldn't safely recover the incremental review. No full review was started, and the last reviewed checkpoint was preserved. Retry later, or explicitly request a full review by commenting @coderabbitai full review.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c25bed3d-5f5f-4400-b7d6-ca761c33bf4c
Commits

Reviewing files that changed from the base of the PR and between 0ef88c1 and 4edcb65.

Files ignored due to path filters: 1
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
Files selected for processing: 41
  • config/patches/@xterm__addon-webgl@0.20.0-beta.300.patch
  • config/patches/xterm-src/@xterm__addon-webgl@0.20.0-beta.300.src.patch
  • src/main/ghostty/mapper.test.ts
  • src/main/ghostty/mapper.ts
  • src/main/window/createMainWindow.test.ts
  • src/main/window/createMainWindow.ts
  • src/main/window/window-material-options.test.ts
  • src/main/window/window-material-options.ts
  • src/preload/api/app-api.ts
  • src/preload/api/platform-bridge.ts
  • src/renderer/src/app-shell/use-document-appearance.ts
  • src/renderer/src/assets/main.css
  • src/renderer/src/components/onboarding/ThemeStep.tsx
  • src/renderer/src/components/settings/AppearanceInterfaceSection.tsx
  • src/renderer/src/components/settings/AppearancePane.tsx
  • src/renderer/src/components/settings/TerminalWindowSection.tsx
  • src/renderer/src/components/settings/WindowMaterialSetting.tsx
  • src/renderer/src/components/settings/appearance-search.ts
  • src/renderer/src/components/settings/setting-labels.ts
  • src/renderer/src/components/settings/terminal-window-setup-search.ts
  • src/renderer/src/components/tab-group/TabGroupPanel.tsx
  • src/renderer/src/components/tab-group/TabGroupSplitLayout.tsx
  • src/renderer/src/components/terminal-pane/terminal-appearance.ts
  • src/renderer/src/components/ui/context-menu.tsx
  • src/renderer/src/components/ui/dropdown-menu.tsx
  • src/renderer/src/hooks/use-media-query.ts
  • src/renderer/src/hooks/use-reduce-visual-effects.ts
  • src/renderer/src/hooks/usePrefersReducedMotion.ts
  • src/renderer/src/i18n/en-runtime-required.json
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/lib/pane-manager/pane-manager-types.ts
  • src/renderer/src/lib/window-material-runtime.ts
  • src/renderer/src/main.tsx
  • src/renderer/src/popout.tsx
  • src/renderer/src/space.tsx
  • src/renderer/src/web/preload-api/web-platform-api.ts
  • src/shared/default-global-settings.ts
  • src/shared/global-settings-base.ts
  • src/shared/global-settings-types.ts
  • src/shared/window-material.test.ts
  • src/shared/window-material.ts
Files with no reviewable changes: 2
  • src/renderer/src/lib/pane-manager/pane-manager-types.ts
  • src/shared/global-settings-types.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review.

Walkthrough

The change adds host-aware window materials, including off, translucent, and blur options, and connects material resolution to native window creation and renderer startup. It replaces the Window Blur setting with Window Material and adds a Reduce Visual Effects setting. Renderer styles and terminal rendering now account for active materials and reduced effects. Ghostty blur keys map to material values, and the WebGL patch updates transparent frame rendering.

Severity of issue fixed: Medium

Merge Risk: Minimal · previous review 4edcb

No actionable material-setting failure was established. The change is mergeable after normal checks; the reported Windows and Linux hardware validation remains outstanding.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 34.38% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly identifies the main change: functional background opacity and blur across macOS, Windows, and Linux.
Linked Issues check Passed Coding requirements from the four direct linked issues are implemented in the PR summary and diff summary. No repository inspection tools were available, so this verdict rests on the supplied summary …
Out of Scope Changes check Passed Every changed area connects to the window-material model or to the four linked issues.

- Window material core (shared/window-material.ts, main window creation, window-material-options.ts, preload pla…
Description check Passed The description is comprehensive and covers the change, rationale, linked issues, visual proof, testing, platform coverage, performance, compatibility, tradeoffs, review history, and remaining validat…
Full details: Linked Issues check

Explanation

Coding requirements from the four direct linked issues are implemented in the PR summary and diff summary. No repository inspection tools were available, so this verdict rests on the supplied summary and PR description. The remaining gaps are runtime checks, not missing code.

[#8797, macOS] The issue asks for vibrancy that shows the desktop, without transparent: true and without an opaque blur-surface background. window-material-options.ts uses vibrancy 'under-window' with visualEffectState 'active' and a clear background for Blurred. It sets no transparent flag. window-material.ts engages the material only when Background Opacity is below 1. Unit tests exist in window-material-options.test.ts and window-material.test.ts. Native vibrancy was checked only through renderer alpha in CDP, not in a visible window.

[#36196, Linux] The issue asks that opacity below 1 show the desktop after restart, with compositor blur. Linux Translucent creates a transparent window with a clear background. The PR removes the opaque html/body paint under the material and keeps one translucent paint in the split root. The legacy Window Blur setting is not promoted to a material on Linux, so Linux users must select Translucent. This is an explicit design choice in the PR, not a missing feature. Linux was not verified in a running app.

[#6491] The issue asks that the terminal background stay stable on redraw when transparency is on. The WebGL addon patch (xterm-src/@xterm__addon-webgl patch) uses separate alpha blending and clears the framebuffer before each frame when allowTransparency is set. Opaque terminals skip this block. The CDP redraw sampling in the PR shows stable pixels, and the patch pipeline has tests (regenerate-xterm-patches.test.mjs). No unit test checks the WebGL blend behavior directly.

[#20936] The issue asks for a persistent option that removes modal backdrop blur and uses solid or semi-opaque dialog surfaces. reduceVisualEffects sets data-reduce-effects. That switch removes the workspace filter blur, removes backdrop-filter, and makes floating and dialog surfaces solid. The setting is stored in global settings, so it persists across restarts. It is searchable through appearance-search.ts and AppearancePane tests.

The linked issues have no non-coding requirements that affect this verdict.

Full details: Out of Scope Changes check

Explanation

Every changed area connects to the window-material model or to the four linked issues.

  • Window material core (shared/window-material.ts, main window creation, window-material-options.ts, preload platform argument, renderer runtime helper): implements the linked requirements.
  • Terminal appearance, xterm theme, pane-manager-types paneBackground removal, TabGroupPanel and TabGroupSplitLayout seam and body markers, main.css layers: implement the single translucent paint and the opaque-base fixes that the material needs.
  • Reduce Visual Effects: context-menu.tsx, dropdown-menu.tsx, popout.tsx, space.tsx, use-document-appearance.ts, use-reduce-visual-effects.ts, and the AppearanceInterfaceSection and appearance-search entries: implement [#20936].
  • use-media-query.ts and usePrefersReducedMotion.ts: a shared hook replaces a duplicated matchMedia implementation. It supports the reduced-transparency check. This is a small refactor with a stated reason.
  • Settings UI (WindowMaterialSetting.tsx, TerminalWindowSection.tsx, setting-labels, terminal-window-setup-search, localization files), ThemeStep.tsx, global settings types, default settings, web-platform-api: connect the Window Blur switch to the new Window Material select, which the PR describes as the user-facing change for [#8797], [#36196] and [#6491].
  • Ghostty mapper and mapper tests: map the background-blur key to the new material model. This keeps the import consistent with the new Blurred and Translucent values. It is a consequential change, and the PR describes it.
  • Migration (windowBackgroundBlur mirror on write, legacy read on Linux): supports the stated rollback and back-compat behavior.

The PR does not change IPC, RPC, or mobile protocol. Secondary windows stay opaque, which limits the blast radius. The PR lists #19301, #23305 and #24751 only as scope notes, and it does not implement app-chrome transparency or per-panel workspace opacity. No change is unrelated to the linked issues or the material model.

Full details: Description check

Explanation

The description is comprehensive and covers the change, rationale, linked issues, visual proof, testing, platform coverage, performance, compatibility, tradeoffs, review history, and remaining validation. It does not use every template heading exactly, and it does not include the template checklist or an explicit AI disclosure section, but the required information is largely present.

Full details: Docstring Coverage

Explanation

Docstring coverage is 34.38% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 32 functions across 34 files. (4 skipped: 4 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@nwparker
nwparker force-pushed the nwparker/theme-window-material branch 2 times, most recently from 4edcb65 to 040af97 Compare October 31, 2026 05:04
@nwparker

Copy link
Copy Markdown
Contributor Author

Phase 2: cross-platform verification on real machines

I built this branch on each host from a fresh clone and ran it as a visible window. Every host used its own profile and its own debugging port. All screenshots are real OS captures (import -window root on X11, CopyFromScreen in the Windows session), so the desktop really is behind the window.

Host OS / compositor Result
openclaw Ubuntu, GNOME/Mutter on X11 (live session :3) ✅ Translucent shows the wallpaper and other windows through terminals in dark and light. Chrome stays opaque. No black window. Resize works with no artifacts (no window shadow, as expected for ARGB windows). The modal keeps an opaque base. Reduce Visual Effects gives an opaque window and removes the modal blur.
awin Windows 11 25H2 (build 26200), Intel GPU, RDP session ✅ Acrylic resolves on 22H2+ and the window never paints black. Over RDP, DWM draws system backdrops as their solid fallback tint (measured terminal rgb(43,46,53) = 0.8 × theme over a grey fallback), so live blur can't be seen there. Needs a console session to see real acrylic.
win-lowspec Windows 11 25H2 (26200), Intel HD 530 ⚠️ Not pre-22H2, so it exercises the same path as awin. The native-module rebuild failed during pnpm install on this host, so it was not run. The pre-22H2 → Opaque fallback is covered only by the unit test.
omarchy Arch + Hyprland ⚠️ Branch built and launched, and the legacy migration was verified. The session is locked (hyprlock), so the compositor showed only the lock screen and no window screenshot was possible. Hyprland also had decoration:blur:enabled = false.
m4air macOS 26.6.2, Apple M4 ⚠️ Material resolution and migration verified (active: "blur"). The session sits at loginwindow (locked), and the serve process lacks Screen Recording permission, so screencapture shows only the wallpaper. A locked screen doesn't composite our window, so WindowServer/GPU numbers there aren't meaningful. powermetrics needs sudo, which isn't available.

Migration from the old Window Blur switch (old shape windowBackgroundBlur: true, no windowMaterial, opacity 0.8, then restart):

  • macOS (m4air): becomes Blurred. Main log: material blur.
  • Windows (awin): becomes Blurred (acrylic). Main log: [ws-window] material blur 10.0.26200 false.
  • Linux (openclaw and omarchy): stays Opaque, as designed. Linux users opt in to Translucent explicitly.

Performance (visible windows):

Host Config Idle Fast output (yes / PowerShell loop)
openclaw (GNOME X11) Opaque compositor 0.2–0.3% CPU, Orca 4–5% compositor 22.1 / 22.2% CPU, Orca 193–207%
openclaw (GNOME X11) Translucent compositor 0.2–0.3%, Orca 3–5% compositor 36.8 / 37.0% CPU, Orca 192–197%
awin (Win 11, RDP) Opaque Orca 1.0% CPU, dwm GPU 0.0% Orca 162% CPU, Orca GPU 4.5%, dwm GPU 4.7%
awin (Win 11, RDP) Acrylic Orca 1.4% CPU, dwm GPU 1.0% Orca 171% CPU, Orca GPU 4.6%, dwm GPU 5.9%
  • Idle cost: none on either OS.
  • Linux: under sustained full-screen output, Mutter spends about 15% of a core more blending the ARGB window. That's the inherent cost of a translucent window, and it only applies when the user opts in.
  • Windows: the acrylic delta is small, though over RDP DWM uses the fallback rather than live blur.
  • macOS: WindowServer cost could not be measured on any visible Mac this round. It is still the open item.

WebGL / canvas leak check (toggling GPU acceleration off and on six times):

Pre-existing, dev-only, not caused by this PR: on Linux, Orca terminal shells kept alive across an app restart inherit the main process's --remote-debugging-port listening socket. A relaunch on the same port then can't bind CDP until those shells exit.

Linux GNOME translucent (dark, light, resized, modal) · Reduce Visual Effects · Windows 11 over RDP:






@nwparker

Copy link
Copy Markdown
Contributor Author

Phase 3: macOS visible-window verification and cost (m4air)

m4air (macOS 26.6.2, Apple M4) was unlocked for this round. I built this branch (9343a825b2) and origin/main (119aafe0ca) from a fresh clone. Each ran as a dev build with its own profile and CDP port, as a real on-screen window in front of a colourful test pattern (bars, a checkerboard, and big black text). Every shot is a real screencapture of the display. Settings: Background Opacity 0.7 for Translucent/Blurred and 1 for Opaque.

Dark: origin/main · Opaque · Translucent · Blurred
m4air dark

Light: origin/main · Opaque · Translucent · Blurred
m4air light

Modals, Reduce Visual Effects, macOS Reduce transparency
m4air extra

Resize (Translucent, dark and light) and a zoomed Blurred terminal (light on top, dark below; the pattern's text is unreadable and only the colour bleeds through):
resize
blur zoom

Check Result
Translucent shows the desktop ✅ Sharp pattern and text show through the terminal in dark and light. Sidebars, tabs and the status bar stay opaque.
Blurred applies vibrancy ✅ Only blurred colour comes through and the text is unreadable. The under-window material is fairly dense, so at opacity 0.7 the effect is subtle; lower opacity makes it stronger.
origin/main with the same settings Opaque window (as expected: main never engaged a material on macOS)
Native window shadow ✅ Present for Opaque, Translucent and Blurred. Window captures include the shadow margin, and the alpha 5/40/80 px below the edge reads 7/39/87 (Opaque), 7/39/87 (Blurred) and 6/38/85 (Translucent).
Modal keeps an opaque base ✅ No desktop shows through the dimmed workspace behind the New Workspace dialog (dark and light, Blurred).
Resize ✅ No black areas or artifacts after resizing the window to 900×600.
Reduce Visual Effects ✅ Restart gives platform.windowMaterial = off, an opaque body and the data-reduce-effects switch.
macOS Reduce transparency (com.apple.universalaccess reduceTransparency, restored afterwards) ✅ Restart gives prefers-reduced-transparency: true, material off, data-reduce-effects on and an opaque window.

Cost

sudo still needs a password on m4air, so powermetrics was not available. Instead:

  • GPU = AGX Device Utilization % from ioreg (no root needed), 1 s samples.
  • CPU = top -l %CPU for WindowServer and for all of the dev app's processes.

Each phase is 20 samples:

  • idle: prompt only, cursor blinking;
  • stream: yes '<80-char line>' into the terminal.

The window was 1300×820 pt and in front. A mid-run screenshot confirmed this for every sample. Values are medians of 5–7 runs, and the order was interleaved across 3 rounds. Other agents were using the machine at the same time, so single runs are noisy. With no dev app running, GPU was 0.0–0.1% (2.6% once).

Config Idle GPU % Idle WindowServer CPU % Stream GPU % Stream WindowServer CPU % Stream Orca CPU %
origin/main, opacity 0.7 + Window Blur (opaque window) 15.2 (noisy: 2–52) 3.8 43.0 26.1 104
PR Opaque (opacity 1) 5.8 3.5 41.3 25.9 102
PR Blurred (opacity 0.7) 10.9 3.6 57.0 28.2 110
PR Translucent (opacity 0.7) 40.4 3.5 83.5 19.7 109
  • Opaque (the default) matches origin/main.
  • Blurred costs about +5 GPU points idle and +16 under sustained full-speed output, with about +2 points of WindowServer CPU. Renderer CPU is within noise.
  • Translucent (transparent: true) is the expensive one on macOS:
    • About +35 GPU points idle and +42 under output.
    • The idle cost doesn't come from Orca. Orca's own CPU stays at 0.3–2%, there are no running CSS animations, and pausing them or unfocusing the terminal changes nothing (47–51%).
    • It drops to 0.0% when the window is hidden, so it's WindowServer re-compositing the alpha window against the content behind it. Here that content was the user's live, full-screen installed Orca.
    • This is inherent to unblurred see-through windows (Ghostty/iTerm-style) and only applies when the user picks Translucent.
  • The phase-2 body claimed that macOS drops the native shadow for transparent: true. That was wrong: the shadow is present, and the body is now corrected.

Not done this round

  • omarchy (Hyprland):
    • The session is behind Omarchy's quickshell lock screen ("Enter Password" in a real grim capture), and the display was DPMS-off.
    • A locked Hyprland session doesn't composite client windows, so Hyprland blur with Translucent couldn't be checked.
    • The branch is built and running there (--ozone-platform=wayland via an ELECTRON_EXEC_PATH shim, since the default X11 path finds no X server), so the check takes about 5 minutes once the session is unlocked.
    • Hyprland's decoration:blur:enabled is false on that host and would have to be enabled for the test.
  • powermetrics package/GPU power (mW): needs sudo on m4air.

No code bugs found, so there's no code change in this round.

@nwparker

nwparker commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor Author

Phase 3 addendum: powermetrics on m4air

The user enabled passwordless sudo /usr/bin/powermetrics, so I re-ran the A/B with real power numbers. Same setup as the phase-3 comment:

  • a visible 1300×820 pt window in front of a test pattern, verified with a mid-run screenshot;
  • dark theme;
  • powermetrics --samplers cpu_power,gpu_power,tasks -i 1000 -n 20 per phase;
  • 3 interleaved rounds of origin/main and Opaque/Blurred/Translucent; medians are shown.

Stream load is yes '<80-char line>' into the terminal at full speed.

Config Idle GPU power Idle GPU active residency Stream GPU power Stream GPU residency Stream WindowServer CPU (ms/s) Stream Orca CPU (ms/s)
No dev app running (machine baseline) 50 mW 6.1% n/a n/a n/a n/a
origin/main (opacity 0.7 + Window Blur → opaque window) 36 mW 6.5% 536 mW 47.2% 137 480
PR Opaque (opacity 1) 39 mW 6.9% 545 mW 46.6% 135 481
PR Blurred (0.7) 49 mW 8.4% 805 mW 57.1% 157 503
PR Translucent (0.7) 313 mW (50–890) 35.6% (5.6–55.7) 655 mW 83.3% 73 507

Ranges are shown where runs disagreed a lot.

Raw per-round samples
r1 none(no Orca Dev) idle GPUpower_mW=103 GPUresidency%=17.9 Combined_mW=5637 WindowServer_CPUms/s=53.4 Orca_CPUms/s=0.0
r1 pr-blurred(op.7) idle GPUpower_mW=144 GPUresidency%=20.4 Combined_mW=4638 WindowServer_CPUms/s=57.0 Orca_CPUms/s=6.6
r1 pr-blurred(op.7) stream GPUpower_mW=946 GPUresidency%=59.4 Combined_mW=10194 WindowServer_CPUms/s=148.3 Orca_CPUms/s=558.0
r1 pr-translucent(op.7) idle GPUpower_mW=890 GPUresidency%=55.7 Combined_mW=11209 WindowServer_CPUms/s=107.1 Orca_CPUms/s=3.0
r1 pr-translucent(op.7) stream GPUpower_mW=1184 GPUresidency%=87.6 Combined_mW=12298 WindowServer_CPUms/s=95.0 Orca_CPUms/s=590.1
r1 pr-opaque(op1) idle GPUpower_mW=45 GPUresidency%=7.9 Combined_mW=4217 WindowServer_CPUms/s=32.9 Orca_CPUms/s=5.1
r1 pr-opaque(op1) stream GPUpower_mW=572 GPUresidency%=46.6 Combined_mW=8849 WindowServer_CPUms/s=124.8 Orca_CPUms/s=508.8
r1 main(op.7,blur-switch) idle GPUpower_mW=48 GPUresidency%=7.6 Combined_mW=3473 WindowServer_CPUms/s=30.7 Orca_CPUms/s=7.5
r1 main(op.7,blur-switch) stream GPUpower_mW=551 GPUresidency%=47.2 Combined_mW=6710 WindowServer_CPUms/s=140.2 Orca_CPUms/s=480.2
r2 none(no Orca Dev) idle GPUpower_mW=50 GPUresidency%=6.1 Combined_mW=2415 WindowServer_CPUms/s=27.3 Orca_CPUms/s=0.0
r2 pr-blurred(op.7) idle GPUpower_mW=49 GPUresidency%=8.4 Combined_mW=2209 WindowServer_CPUms/s=34.8 Orca_CPUms/s=6.7
r2 pr-blurred(op.7) stream GPUpower_mW=805 GPUresidency%=57.1 Combined_mW=7646 WindowServer_CPUms/s=156.8 Orca_CPUms/s=502.5
r2 pr-translucent(op.7) idle GPUpower_mW=313 GPUresidency%=35.6 Combined_mW=2157 WindowServer_CPUms/s=56.4 Orca_CPUms/s=7.0
r2 pr-translucent(op.7) stream GPUpower_mW=655 GPUresidency%=83.3 Combined_mW=7511 WindowServer_CPUms/s=73.3 Orca_CPUms/s=507.0
r2 pr-opaque(op1) idle GPUpower_mW=39 GPUresidency%=6.9 Combined_mW=2102 WindowServer_CPUms/s=30.2 Orca_CPUms/s=6.7
r2 pr-opaque(op1) stream GPUpower_mW=545 GPUresidency%=47.5 Combined_mW=6788 WindowServer_CPUms/s=136.2 Orca_CPUms/s=480.7
r2 main(op.7,blur-switch) idle GPUpower_mW=36 GPUresidency%=6.5 Combined_mW=3135 WindowServer_CPUms/s=27.4 Orca_CPUms/s=6.7
r2 main(op.7,blur-switch) stream GPUpower_mW=536 GPUresidency%=47.6 Combined_mW=6910 WindowServer_CPUms/s=137.4 Orca_CPUms/s=484.3
r3 none(no Orca Dev) idle GPUpower_mW=13 GPUresidency%=3.9 Combined_mW=1906 WindowServer_CPUms/s=25.5 Orca_CPUms/s=0.0
r3 pr-blurred(op.7) idle GPUpower_mW=21 GPUresidency%=4.5 Combined_mW=1096 WindowServer_CPUms/s=9.1 Orca_CPUms/s=10.0
r3 pr-blurred(op.7) stream GPUpower_mW=135 GPUresidency%=55.5 Combined_mW=4874 WindowServer_CPUms/s=164.8 Orca_CPUms/s=466.1
r3 pr-translucent(op.7) idle GPUpower_mW=50 GPUresidency%=5.6 Combined_mW=522 WindowServer_CPUms/s=8.1 Orca_CPUms/s=9.9
r3 pr-translucent(op.7) stream GPUpower_mW=584 GPUresidency%=83.3 Combined_mW=6937 WindowServer_CPUms/s=64.8 Orca_CPUms/s=488.7
r3 pr-opaque(op1) idle GPUpower_mW=2 GPUresidency%=0.4 Combined_mW=1429 WindowServer_CPUms/s=3.6 Orca_CPUms/s=4.4
r3 pr-opaque(op1) stream GPUpower_mW=516 GPUresidency%=44.6 Combined_mW=6450 WindowServer_CPUms/s=134.9 Orca_CPUms/s=471.1
r3 main(op.7,blur-switch) idle GPUpower_mW=4 GPUresidency%=0.7 Combined_mW=1297 WindowServer_CPUms/s=4.3 Orca_CPUms/s=4.5
r3 main(op.7,blur-switch) stream GPUpower_mW=522 GPUresidency%=44.5 Combined_mW=7306 WindowServer_CPUms/s=130.2 Orca_CPUms/s=478.4
  • Opaque (the default) matches origin/main on every metric.
  • Blurred:
    • idle is at noise level (+10 mW);
    • under sustained full-speed output, GPU residency is about +10 points and WindowServer CPU about +16%;
    • GPU power was 805 mW median, but the samples were noisy (135–946 mW).
  • Translucent:
    • Under output, GPU residency is about 83% vs 47%.
    • The idle cost depends on what is changing behind the window. It was 890 / 313 / 50 mW across rounds, and the 50 mW round was the one where the machine (and the live Orca window behind ours) was quiet.
    • With the window hidden it was 0, as measured before.
    • WindowServer CPU drops under output because it becomes GPU-bound.
    • This confirms the earlier ioreg finding: on macOS Translucent is the costly option and Blurred the cheap one.

No code bugs found and no code changes. omarchy (Hyprland) is still behind its password lock screen. See the follow-up when it's unlocked.

@nwparker

Copy link
Copy Markdown
Contributor Author

Phase 3: Hyprland (omarchy) and perf scan

Hyprland 0.56 (omarchy, Wayland). These are real grim captures of the visible window, tiled over the Omarchy wallpaper.

  • Hyprland's decoration:blur:enabled was turned on at runtime only for the blur shots, then restored to false.
  • Omarchy's stay-awake setting was restored to allow-idle.
  • Omarchy's default window-opacity rule makes every client slightly translucent, including Opaque Orca and the foot terminal. That comes from the host config, not this PR.

hyprland

Zoomed terminal area: Translucent with Hyprland blur off (left: sharp wallpaper) and on (right: blurred).

hyprland blur zoom

Check Result
Translucent shows the desktop ✅ (platform.windowMaterial = translucent, transparent body)
Hyprland blur applies to it ✅ The wallpaper behind the terminal is blurred with blur on and sharp with it off. Chrome stays opaque.
Light theme ✅
Reduce Visual Effects ✅ material off, opaque body, data-reduce-effects set
Compositor cost Hyprland CPU unchanged (about 5.5% under output). GPU busy is +4 points under output for Translucent and +2 points idle with Hyprland blur. Numbers are in the PR body's Perf section.

Perf scan over the whole diff: no findings. The PR body now opens with a self-contained Perf section. It covers:

  • macOS powermetrics;
  • Linux (Hyprland and GNOME) and Windows compositor numbers;
  • a CDP renderer trace (paint, composite, GPU, layout, style, script, JS heap) for origin/main vs Opaque vs material on;
  • a plain-language explanation of where the cost comes from and which setting to choose.

The only renderer-side delta with the material on is about +7.5 ms/s of compositing during full-speed output (about 0.13 ms per frame). Total renderer work is unchanged.

@nwparker
nwparker force-pushed the nwparker/theme-window-material branch 2 times, most recently from 50db96a to 5b71d48 Compare November 1, 2026 03:12
…nd Linux

One window material model (opaque / translucent / blurred): macOS vibrancy without transparent, Windows 11 22H2+ acrylic, Linux transparent windows. Terminals paint their translucent background once, the transparent WebGL frame clears before drawing (#6491), and Reduce Visual Effects drops modal/backdrop blur and honors prefers-reduced-transparency.
@nwparker
nwparker force-pushed the nwparker/theme-window-material branch from af51398 to c21f61f Compare November 1, 2026 09:25
@nwparker
nwparker merged commit 4b4c833 into main Nov 1, 2026
71 checks passed
@nwparker
nwparker deleted the nwparker/theme-window-material branch November 1, 2026 09:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant