Skip to content

[bug] setTheme resolves but no longer repaints the standard macOS title bar on macOS 26 (raw tao 0.35.3 + wry 0.55.1 works) #15707

Description

@acxtodd

Describe the bug

On macOS 26.5.1 (25F80), Window.setTheme('dark'|'light') (and app.setTheme) resolves successfully but the standard title bar never repaints in a Tauri 2.11.2 app — while the identical underlying call works correctly in a raw tao 0.35.3 + wry 0.55.1 program on the same machine.

Evidence gathered before filing:

  • The IPC resolves (no capability error; core:window:allow-set-theme granted).
  • The appearance override IS applied at the AppKit level: inside the webview, matchMedia('(prefers-color-scheme: dark)') flips in lockstep with setTheme — so NSApplication.setAppearance took effect and reached the view hierarchy. Only the window-server-drawn standard title bar stays in the OS appearance.
  • window.theme() is not usable as verification: tao returns the optimistically cached shared_state.current_theme (set by set_theme itself), and the KVO handler suppresses ThemeChanged when the cache already matches.
  • Not the fix(macos): run app.set_theme on main thread #13443 class: in 2.11.2 the window set_theme is dispatched through the event-loop proxy (tauri-runtime-wry lib.rs:2528:3675) and runs on the main thread; app.setTheme behaves identically to window.setTheme here (both resolve, neither repaints the bar).
  • Single-variable bisection (all on the same machine, human-observed):
Configuration Standard title bar follows setTheme?
tao 0.35.3, bare binary ✅ flips
tao + embedded Info.plist ✅ flips
tao + Info.plist + wry 0.55.1 WKWebView attached (goes through wry's setTitlebarSeparatorStyle path) ✅ flips
minimal Tauri 2.11.2 (default config, static HTML, no plugins, withGlobalTauri) ❌ never flips (setTheme resolves on-screen)

So the regression surface is bracketed inside tauri/tauri-runtime-wry window/event-loop wiring, between raw tao+wry and a default Tauri app.

Reproduction

Minimal Tauri app (fails — bar never flips; src/main.rs is just tauri::Builder::default().run(tauri::generate_context!())):

tauri.conf.json:

{
  "productName": "tauri-min", "version": "0.1.0", "identifier": "com.diag.taurimin",
  "build": { "frontendDist": "./dist" },
  "app": { "withGlobalTauri": true, "windows": [ { "title": "TAURI MIN", "width": 700, "height": 400 } ] },
  "bundle": { "active": false }
}

capability: { "identifier": "default", "windows": ["main"], "permissions": ["core:default", "core:window:allow-set-theme"] }

dist/index.html:

<script>
  const w = window.__TAURI__.window.getCurrentWindow();
  let themes = ['dark','light','dark','light','dark','light','dark','light'], i = 0;
  const tick = async () => {
    if (i >= themes.length) return;
    const t = themes[i++];
    try { await w.setTheme(t); document.body.textContent = 'setTheme ' + t + ' OK (' + i + '/8)'; }
    catch (e) { document.body.textContent = 'setTheme FAILED: ' + e; }
    setTimeout(tick, 4000);
  };
  setTimeout(tick, 4000);
</script>

Control program (works — bar flips every cycle; same behavior with an Info.plist embedded via -sectcreate and with a wry 0.55.1 webview attached via WebViewBuilder::new().with_url("about:blank").build(&window)):

use std::{thread, time::Duration};
use tao::{event::{Event, WindowEvent}, event_loop::{ControlFlow, EventLoopBuilder}, window::{Theme, WindowBuilder}};

fn main() {
  let event_loop = EventLoopBuilder::<Theme>::with_user_event().build();
  let window = WindowBuilder::new().with_title("TAO THEME ISOLATION").build(&event_loop).unwrap();
  let proxy = event_loop.create_proxy();
  thread::spawn(move || {
    let mut next = Theme::Dark;
    for _ in 0..8 {
      thread::sleep(Duration::from_secs(4));
      let _ = proxy.send_event(next);
      next = if matches!(next, Theme::Dark) { Theme::Light } else { Theme::Dark };
    }
    thread::sleep(Duration::from_secs(4));
    std::process::exit(0);
  });
  event_loop.run(move |event, _, control_flow| {
    *control_flow = ControlFlow::Wait;
    match event {
      Event::UserEvent(theme) => { window.set_theme(Some(theme)); println!("set_theme({theme:?})"); }
      Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => *control_flow = ControlFlow::Exit,
      _ => (),
    }
  });
}

Expected behavior

The standard macOS title bar follows setTheme, as it does in the raw tao 0.35.3 + wry 0.55.1 control on the same OS.

Full tauri info

  • macOS 26.5.1 (25F80), Apple Silicon
  • tauri 2.11.2 / tauri-runtime-wry 2.11.2 / tao 0.35.3 / wry 0.55.1 / @tauri-apps/api 2.11.0 / CLI 2.11.2

(Happy to run further bisection builds — the two repro programs above bracket the defect.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    ai-slopLow effort content, see https://github.com/tauri-apps/tauri?tab=contributing-ov-file#ai-tool-policyplatform: macOSstatus: needs triageThis issue needs to triage, applied to new issuestype: bug

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions