host: a pane's own theme wins over a theme preview

A preview (the Theme Builder's, developer mode's, or one an automation left on through theme_preview)
replaced every site tab's theme, pane themes included, also on reload; "Theme for this pane" then
showed its choice checked while the page kept the preview. Reproduced live: a preview of Windows 98
turned a Paper pane into Windows 98 until the preview ended. Now a pane with its own theme keeps it;
every other tab shows the preview, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
CRTkafa 2026-10-01 16:51:42 +07:00
parent 4d806b647d
commit ef3a5f6f7a
2 changed files with 26 additions and 9 deletions

View file

@ -314,6 +314,17 @@ impl ThemeScopes {
pub fn for_tab(&self, site: &ThemeSite, global: &ThemeChoice, tab: &str, profile: Option<&str>) -> ThemeChoice {
global.for_theme(site, self.theme_id(site, global, tab, profile))
}
/// What site tab `tab` shows with the Theme Builder's (or developer mode's) `preview` in play: a
/// pane the owner gave a theme of its own keeps it, every other tab shows the preview. A preview
/// replaces the global theme, not a choice made for one pane (a preview left on must not make
/// "Theme for this pane" look broken).
pub fn for_tab_with_preview(&self, site: &ThemeSite, global: &ThemeChoice, preview: Option<&ThemeChoice>, tab: &str, profile: Option<&str>) -> ThemeChoice {
match preview {
Some(preview) if !self.panes.get(tab).is_some_and(|id| global.knows(site, id)) => preview.clone(),
_ => self.for_tab(site, global, tab, profile),
}
}
}
#[cfg(test)]
@ -331,6 +342,17 @@ mod tests {
theme.validate(&SITE).map(|()| theme)
}
#[test]
fn a_preview_replaces_the_global_theme_but_not_a_pane_theme() {
let global = ThemeChoice { preset: "paper".into(), ..ThemeChoice::default_for(&SITE) };
let preview = ThemeChoice { preset: "nord".into(), ..ThemeChoice::default_for(&SITE) };
let scopes = ThemeScopes { panes: [("pane1".to_string(), "night-ink".to_string())].into_iter().collect(), ..ThemeScopes::default() };
assert_eq!(scopes.for_tab_with_preview(&SITE, &global, Some(&preview), "pane1", None).preset, "night-ink");
assert_eq!(scopes.for_tab_with_preview(&SITE, &global, Some(&preview), "other", None).preset, "nord");
assert_eq!(scopes.for_tab_with_preview(&SITE, &global, None, "pane1", None).preset, "night-ink");
assert_eq!(scopes.for_tab_with_preview(&SITE, &global, None, "other", None).preset, "paper");
}
#[test]
fn page_color_follows_the_shown_theme() {
let preset = |id: &str| ThemeChoice { preset: id.into(), ..ThemeChoice::default_for(&SITE) };

View file

@ -59,17 +59,12 @@ pub fn scopes(app: &AppHandle) -> Result<ThemeScopes, String> {
Ok(state(app)?.scopes.clone())
}
/// The theme for site tab webview `label`: the Theme Builder's preview while one is open, else its
/// pane's, its account's or the global theme.
/// The theme for site tab webview `label`: its pane's own theme; else the Theme Builder's preview
/// while one is open; else its account's or the global theme.
fn tab_theme(app: &AppHandle, themes: &ThemeConfig, state: &Themes, label: &str) -> ThemeChoice {
let global = state.theme.clone().unwrap_or_else(|| ThemeChoice::default_for(themes.site));
match &state.preview {
Some(preview) => preview.clone(),
None => {
let tab = crate::policy::tab_id(label).unwrap_or_default();
state.scopes.for_tab(themes.site, &global, tab, tab_profile(app, label).as_deref())
}
}
let tab = crate::policy::tab_id(label).unwrap_or_default();
state.scopes.for_tab_with_preview(themes.site, &global, state.preview.as_ref(), tab, tab_profile(app, label).as_deref())
}
fn background(theme: &ThemeChoice, themes: &ThemeConfig) -> Option<tauri::window::Color> {