From 76c63cbfd13d35374ddbefb43382ad1e2281c336 Mon Sep 17 00:00:00 2001 From: iamtoruk Date: Tue, 18 Aug 2026 04:37:27 -0700 Subject: [PATCH] windows: cfg-gate the tray badge so the Linux build has no dead code The spend badge is a second tray icon carrying the number as its bitmap, which only the Tauri tray backend provides; Linux runs its own SNI tray and has no equivalent. The module was compiled there anyway, so every item in it - and the two tray ids in lib.rs - tripped dead_code under clippy -D warnings on the ubuntu leg. - `mod tray_badge` and TRAY_ID / BADGE_TRAY_ID are now cfg(not(linux)), so the code is absent on Linux rather than present and unused. - `set_tray_badge` reports the badge as unsupported on Linux instead of returning a success that never happened. - The frontend hides the control wherever it is unsupported, behind TRAY_BADGE_SUPPORTED in lib/platform.ts - one constant, three call sites (the settings row, the footer menu item, and the effect that would otherwise invoke the command). - AppState's `linux_tray` field is dropped: it was written and never read, which is the same lint one file over. init_tray_linux already owns the handle. Checked rather than reasoned: `cargo clippy --all-targets -- -D warnings` passes on macOS, and a scratch copy of the crate with the linux/macos cfg arms swapped (so a normal macOS clippy selects everything a Linux build would keep, and drops everything it would drop) also passes. Reinstating the ungated `mod tray_badge` in that copy reproduces the exact CI failure, so the check is real. It stubs tray_linux.rs, whose ksni and png deps do not build here - that file is still only covered by CI's ubuntu leg. --- windows/DEVELOPMENT.md | 6 ++++ windows/src-tauri/src/lib.rs | 36 +++++++++++++----------- windows/src/App.tsx | 4 ++- windows/src/components/FooterBar.tsx | 5 +++- windows/src/components/SettingsPanel.tsx | 10 ++++--- windows/src/lib/platform.ts | 6 ++++ 6 files changed, 44 insertions(+), 23 deletions(-) diff --git a/windows/DEVELOPMENT.md b/windows/DEVELOPMENT.md index fee5a3d2..9df090a5 100644 --- a/windows/DEVELOPMENT.md +++ b/windows/DEVELOPMENT.md @@ -8,6 +8,12 @@ Linux (ksni / AppIndicator) support is compiled and kept working for dev, but it **experimental and unreleased** - Linux users should use the GNOME extension in `../gnome/`. The releases this repo cuts from here are Windows only. +Not everything crosses over: the spend badge is a second tray icon carrying the number as its +bitmap, which only the Windows notification area provides. `tray_badge` is compiled out on +Linux, the `set_tray_badge` command reports it as unsupported there, and the frontend hides +the control behind `TRAY_BADGE_SUPPORTED` in `src/lib/platform.ts`. Anything else that is +Windows-only must be cfg-gated the same way, or the ubuntu leg of CI fails on dead code. + ## Architecture ``` diff --git a/windows/src-tauri/src/lib.rs b/windows/src-tauri/src/lib.rs index 6064a916..64e67111 100644 --- a/windows/src-tauri/src/lib.rs +++ b/windows/src-tauri/src/lib.rs @@ -3,6 +3,10 @@ mod cli; mod config; mod fx; mod plan; +/// The spend-in-the-tray badge is a second tray icon, which only the Tauri tray backend +/// provides; Linux runs its own SNI tray (`tray_linux`) and has no equivalent, so the +/// whole module is compiled out there rather than sitting unused. +#[cfg(not(target_os = "linux"))] mod tray_badge; #[cfg(target_os = "linux")] mod tray_linux; @@ -26,9 +30,11 @@ use crate::cli::CodeburnCli; use crate::config::CurrencyConfig; use crate::fx::FxCache; +#[cfg(not(target_os = "linux"))] const TRAY_ID: &str = "codeburn-tray"; /// Second tray icon that carries today's spend as text, sitting next to the logo. The -/// closest Windows and Linux panels get to the macOS menubar title. +/// closest the Windows notification area gets to the macOS menubar title. +#[cfg(not(target_os = "linux"))] const BADGE_TRAY_ID: &str = "codeburn-badge"; const POPOVER_LABEL: &str = "popover"; @@ -41,8 +47,6 @@ pub struct AppState { pub config: Mutex, pub fx: FxCache, pub plan: plan::PlanClient, - #[cfg(target_os = "linux")] - pub linux_tray: tray_linux::LinuxTrayHandle, } #[cfg_attr(mobile, tauri::mobile_entry_point)] @@ -51,24 +55,18 @@ pub fn run() { .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_opener::init()) .setup(|app| { - #[cfg(target_os = "linux")] - let linux_tray = tray_linux::LinuxTrayHandle::empty(); - - let state = AppState { + app.manage(AppState { cli: Mutex::new(CodeburnCli::resolve()), config: Mutex::new(CurrencyConfig::load_or_default()), fx: FxCache::new(), plan: plan::PlanClient::new(), - #[cfg(target_os = "linux")] - linux_tray: linux_tray.clone(), - }; - app.manage(state); + }); #[cfg(not(target_os = "linux"))] build_tray_tauri(app.handle())?; #[cfg(target_os = "linux")] - init_tray_linux(app.handle().clone(), linux_tray); + init_tray_linux(app.handle().clone(), tray_linux::LinuxTrayHandle::empty()); if let Some(window) = app.get_webview_window(POPOVER_LABEL) { let _ = window.hide(); @@ -466,6 +464,14 @@ mod commands { /// `text` is a short spend string ("$87", "142", "1.2K"); `None` hides the badge icon. #[tauri::command] pub fn set_tray_badge(app: AppHandle, text: Option) -> Result<(), String> { + #[cfg(target_os = "linux")] + { + // Unreachable from the UI: the frontend hides the control wherever the badge is + // unsupported (lib/platform.ts). Saying so beats reporting a success that never + // happened. + let _ = (app, text); + Err("the tray spend badge needs a second tray icon, which the Linux SNI tray does not provide".to_string()) + } #[cfg(not(target_os = "linux"))] { let Some(badge) = app.tray_by_id(super::BADGE_TRAY_ID) else { @@ -487,12 +493,8 @@ mod commands { badge.set_visible(false).map_err(|e| e.to_string())?; } } + Ok(()) } - #[cfg(target_os = "linux")] - { - let _ = (app, text); - } - Ok(()) } #[tauri::command] diff --git a/windows/src/App.tsx b/windows/src/App.tsx index af923314..77281597 100644 --- a/windows/src/App.tsx +++ b/windows/src/App.tsx @@ -8,6 +8,7 @@ import { USD, formatCurrency, trayBadgeText } from './lib/currency' import { PayloadCache } from './lib/cache' import { relativePast } from './lib/dates' import { applyTheme, currentTheme, readSetting, writeSetting } from './lib/settings' +import { TRAY_BADGE_SUPPORTED } from './lib/platform' import { AgentTabStrip, detectedProviders } from './components/AgentTabStrip' import type { Provider } from './components/AgentTabStrip' import { ModelsSection } from './components/ModelsSection' @@ -65,7 +66,7 @@ export function App() { const [version, setVersion] = useState('') const [lastUpdated, setLastUpdated] = useState(null) const [theme, setTheme] = useState(() => currentTheme()) - const [trayBadge, setTrayBadge] = useState(() => readSetting('trayBadge') !== 'off') + const [trayBadge, setTrayBadge] = useState(() => TRAY_BADGE_SUPPORTED && readSetting('trayBadge') !== 'off') const [showSettings, setShowSettings] = useState(false) // The window starts hidden and is shown by a tray click, which emits `codeburn://shown`. const [popoverVisible, setPopoverVisible] = useState(false) @@ -216,6 +217,7 @@ export function App() { }, [todayCost, currency]) useEffect(() => { + if (!TRAY_BADGE_SUPPORTED) return const text = trayBadge && todayCost !== null ? trayBadgeText(todayCost, currency) : null invoke('set_tray_badge', { text }).catch(err => setError(`Tray badge: ${String(err)}`)) }, [todayCost, currency, trayBadge]) diff --git a/windows/src/components/FooterBar.tsx b/windows/src/components/FooterBar.tsx index 68490006..6ec88ed1 100644 --- a/windows/src/components/FooterBar.tsx +++ b/windows/src/components/FooterBar.tsx @@ -1,5 +1,6 @@ import type { CurrencyState } from '../lib/currency' import { CURRENCY_CODES } from '../lib/currency' +import { TRAY_BADGE_SUPPORTED } from '../lib/platform' import { DropMenu } from './DropMenu' import { CoinIcon, DownloadIcon, EllipsisIcon, RefreshIcon, TerminalIcon } from './Icons' @@ -64,7 +65,9 @@ export function FooterBar({ className="dropmenu-more" items={[ { id: 'settings', label: settingsOpen ? 'Back to overview' : 'Settings…' }, - { id: 'badge', label: "Show today's cost in tray", checked: trayBadge, separatorBefore: true }, + ...(TRAY_BADGE_SUPPORTED + ? [{ id: 'badge', label: "Show today's cost in tray", checked: trayBadge, separatorBefore: true }] + : []), { id: 'theme', label: themeLabel }, { id: 'quit', label: 'Quit CodeBurn', separatorBefore: true }, ]} diff --git a/windows/src/components/SettingsPanel.tsx b/windows/src/components/SettingsPanel.tsx index 766e81ca..5e9e3fae 100644 --- a/windows/src/components/SettingsPanel.tsx +++ b/windows/src/components/SettingsPanel.tsx @@ -3,7 +3,7 @@ import { invoke } from '@tauri-apps/api/core' import { openUrl } from '@tauri-apps/plugin-opener' import type { CurrencyState } from '../lib/currency' import { CURRENCY_CODES } from '../lib/currency' -import { homePath } from '../lib/platform' +import { homePath, TRAY_BADGE_SUPPORTED } from '../lib/platform' import type { CliStatus } from './SetupState' import { DropMenu } from './DropMenu' import { ChevronDown, ChevronRight } from './Icons' @@ -70,9 +70,11 @@ export function SettingsPanel({ {loginError &&
{loginError}
} - - onTrayBadge(!trayBadge)} /> - + {TRAY_BADGE_SUPPORTED && ( + + onTrayBadge(!trayBadge)} /> + + )}
diff --git a/windows/src/lib/platform.ts b/windows/src/lib/platform.ts index a7a1733f..538d9f3f 100644 --- a/windows/src/lib/platform.ts +++ b/windows/src/lib/platform.ts @@ -8,3 +8,9 @@ const SEP = IS_WINDOWS ? '\\' : '/' export function homePath(...parts: string[]): string { return [HOME, ...parts].join(SEP) } + +/// Today's spend in the tray is a second tray icon carrying the number as its bitmap. Only +/// the Windows notification area gives us one; the Linux SNI tray has no equivalent, and +/// macOS ships the Swift menubar instead. Where this is false the control is hidden and the +/// Rust command is never called. +export const TRAY_BADGE_SUPPORTED = IS_WINDOWS