Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

## [Windows] Unreleased

- Add **Color by provider** to Settings > Menu Bar (off by default): tints the tray usage bars and percent text with the shown provider's brand or accent color, keeps pace colors and the error colors unchanged, and falls back to the usual colors when a tint would be hard to see on the tray background (upstream #4321).
- OpenAI API: each Admin refresh also reads the last hour of 1-minute usage from the Admin Usage API (input plus output tokens, cached input included, Batch API traffic kept out of limit headroom) and carries a Tokens/min grid with a reporting frontier, so buckets that arrive late show as pending instead of zero. The backend can also load 15-minute, 1-hour or 6-hour windows, read per-project rate-limit caps, and measure headroom against manual per-model organization limits stored in settings. The per-refresh request can be turned off, and a failure never affects the daily usage. The chart, menu bar token and Settings controls come in a later release (upstream #4495).
- **Claude**: show the plan renewal or expiry date from claude.ai billing under the quota rows when your cookie session supplies it, and make saved limit-reset credits, Extra usage (including a prepaid balance), and plan dates independently selectable under Visible usage items. Existing settings keep all three visible; Admin API spend is unaffected (upstream #4324, #4484).
- Devin: use Devin's official transparent hexagon mark for the provider icon in Settings and the CLI dashboard, instead of the settings-gear badge and the letter fallback (upstream #4458).
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,7 @@ pub struct SettingsSnapshot {
highlight_menu_cards_on_hover: bool,
workday_tick_style: codexbar::settings::WorkdayTickStyle,
menu_bar_color_pace: bool,
menu_bar_color_by_provider: bool,
menu_bar_pace_reserve_positive: bool,
providers_sorted_alphabetically: bool,
show_as_used: bool,
Expand Down Expand Up @@ -1185,6 +1186,7 @@ impl From<Settings> for SettingsSnapshot {
highlight_menu_cards_on_hover: settings.highlight_menu_cards_on_hover,
workday_tick_style: settings.workday_tick_style,
menu_bar_color_pace: settings.menu_bar_color_pace,
menu_bar_color_by_provider: settings.menu_bar_color_by_provider,
menu_bar_pace_reserve_positive: settings.menu_bar_pace_reserve_positive,
providers_sorted_alphabetically: settings.providers_sorted_alphabetically,
show_as_used: settings.show_as_used,
Expand Down
25 changes: 25 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub struct SettingsUpdate {
pub highlight_menu_cards_on_hover: Option<bool>,
pub workday_tick_style: Option<codexbar::settings::WorkdayTickStyle>,
pub menu_bar_color_pace: Option<bool>,
pub menu_bar_color_by_provider: Option<bool>,
pub menu_bar_pace_reserve_positive: Option<bool>,
pub providers_sorted_alphabetically: Option<bool>,
pub show_as_used: Option<bool>,
Expand Down Expand Up @@ -185,6 +186,7 @@ impl SettingsUpdate {
|| self.menu_bar_shows_percent.is_some()
|| self.menu_bar_icon_style.is_some()
|| self.menu_bar_color_pace.is_some()
|| self.menu_bar_color_by_provider.is_some()
|| self.show_as_used.is_some()
|| self.reset_time_relative.is_some()
|| self.menu_bar_display_mode.is_some()
Expand Down Expand Up @@ -395,6 +397,9 @@ impl SettingsUpdate {
if let Some(v) = self.menu_bar_color_pace {
settings.menu_bar_color_pace = v;
}
if let Some(v) = self.menu_bar_color_by_provider {
settings.menu_bar_color_by_provider = v;
}
if let Some(v) = self.menu_bar_pace_reserve_positive {
settings.menu_bar_pace_reserve_positive = v;
}
Expand Down Expand Up @@ -1140,6 +1145,26 @@ mod tests {
assert!(!settings.menu_bar_color_pace);
}

#[test]
fn apply_display_settings_updates_color_by_provider() {
let mut settings = Settings::default();
assert!(!settings.menu_bar_color_by_provider);

SettingsUpdate {
menu_bar_color_by_provider: Some(true),
..Default::default()
}
.apply_display_settings(&mut settings);
assert!(settings.menu_bar_color_by_provider);

SettingsUpdate {
menu_bar_color_by_provider: Some(false),
..Default::default()
}
.apply_display_settings(&mut settings);
assert!(!settings.menu_bar_color_by_provider);
}

#[test]
fn apply_display_settings_updates_pace_sign() {
let mut settings = Settings::default();
Expand Down
87 changes: 77 additions & 10 deletions apps/desktop-tauri/src-tauri/src/tray_presentation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,32 @@ use chrono::{DateTime, Utc};
use codexbar::core::{IconLane, ProviderId, UsagePace};
use codexbar::settings::{Language, MetricPreference, Settings, TrayIconMode};
use codexbar::tray::{
TrayPaceColor, render_bar_icon_rgba_with_pace, render_percent_icon_rgba_with_pace,
render_stacked_bar_icon_rgba,
TrayPaceColor, render_bar_icon_rgba_tinted, render_percent_icon_rgba_tinted,
render_stacked_bar_icon_rgba_tinted,
};

/// Opt-in provider accent for a tray meter (`menu_bar_color_by_provider`).
type ProviderRgb = (u8, u8, u8);

#[derive(Debug, Clone, Copy, PartialEq)]
enum TrayIconPlan {
Bars {
primary_percent: f64,
secondary_percent: Option<f64>,
has_error: bool,
provider_tint: Option<ProviderRgb>,
},
Percent {
percent: f64,
has_error: bool,
provider_tint: Option<ProviderRgb>,
},
Stacked {
top_percent: f64,
bottom_percent: f64,
has_error: bool,
top_tint: Option<ProviderRgb>,
bottom_tint: Option<ProviderRgb>,
},
}

Expand Down Expand Up @@ -79,6 +86,7 @@ impl<'a> TrayPresentationPlan<'a> {
let prefer_highest = settings.menu_bar_shows_highest_usage;
let selected = pick_tray_provider(&healthy, prefer_highest);
let icon_pace = tray_pace_color(settings, selected, now);
let single_tint = provider_tint(settings, selected);

let (icon, status_rows) = match settings.tray_icon_mode {
TrayIconMode::Stacked => {
Expand All @@ -88,6 +96,8 @@ impl<'a> TrayPresentationPlan<'a> {
top_percent: selected_tray_percents(top, settings).0,
bottom_percent: selected_tray_percents(bottom, settings).0,
has_error,
top_tint: provider_tint(settings, Some(top)),
bottom_tint: provider_tint(settings, Some(bottom)),
},
vec![
TrayStatusRow {
Expand All @@ -114,7 +124,11 @@ impl<'a> TrayPresentationPlan<'a> {
.collect();
(
resolve_single_provider_icon_plan(
settings, percents.0, percents.1, has_error,
settings,
percents.0,
percents.1,
has_error,
single_tint,
),
rows,
)
Expand All @@ -133,7 +147,13 @@ impl<'a> TrayPresentationPlan<'a> {
})
.collect();
(
resolve_single_provider_icon_plan(settings, percents.0, percents.1, has_error),
resolve_single_provider_icon_plan(
settings,
percents.0,
percents.1,
has_error,
single_tint,
),
rows,
)
}
Expand All @@ -149,7 +169,13 @@ impl<'a> TrayPresentationPlan<'a> {
.into_iter()
.collect();
(
resolve_single_provider_icon_plan(settings, percents.0, percents.1, has_error),
resolve_single_provider_icon_plan(
settings,
percents.0,
percents.1,
has_error,
single_tint,
),
rows,
)
}
Expand All @@ -175,20 +201,32 @@ impl<'a> TrayPresentationPlan<'a> {
primary_percent,
secondary_percent,
has_error,
} => render_bar_icon_rgba_with_pace(
provider_tint,
} => render_bar_icon_rgba_tinted(
primary_percent,
secondary_percent,
has_error,
self.icon_pace,
provider_tint,
),
TrayIconPlan::Percent { percent, has_error } => {
render_percent_icon_rgba_with_pace(percent, has_error, self.icon_pace)
}
TrayIconPlan::Percent {
percent,
has_error,
provider_tint,
} => render_percent_icon_rgba_tinted(percent, has_error, self.icon_pace, provider_tint),
TrayIconPlan::Stacked {
top_percent,
bottom_percent,
has_error,
} => render_stacked_bar_icon_rgba(top_percent, bottom_percent, has_error),
top_tint,
bottom_tint,
} => render_stacked_bar_icon_rgba_tinted(
top_percent,
bottom_percent,
has_error,
top_tint,
bottom_tint,
),
}
}

Expand All @@ -213,21 +251,50 @@ fn resolve_single_provider_icon_plan(
primary_percent: f64,
secondary_percent: Option<f64>,
has_error: bool,
provider_tint: Option<ProviderRgb>,
) -> TrayIconPlan {
if settings.menu_bar_icon_style == codexbar::settings::MenuBarIconStyle::Percent {
TrayIconPlan::Percent {
percent: primary_percent,
has_error,
provider_tint,
}
} else {
TrayIconPlan::Bars {
primary_percent,
secondary_percent,
has_error,
provider_tint,
}
}
}

/// Accent tint for the meter of `snapshot` (upstream "Color by provider").
///
/// Opt-in through `menu_bar_color_by_provider`. Uses the effective accent, so
/// a per-provider override applies too. A malformed override yields no tint.
/// Legibility against the tray canvas is checked by the renderer.
fn provider_tint(
settings: &Settings,
snapshot: Option<&ProviderUsageSnapshot>,
) -> Option<ProviderRgb> {
if !settings.menu_bar_color_by_provider {
return None;
}
let id = ProviderId::from_cli_name(&snapshot?.provider_id)?;
parse_hex_rgb(&settings.effective_accent_color(id))
}

/// Parse `#RRGGBB` (the leading `#` is optional and case is ignored).
fn parse_hex_rgb(value: &str) -> Option<ProviderRgb> {
let hex = value.trim().strip_prefix('#').unwrap_or(value.trim());
if hex.len() != 6 || !hex.bytes().all(|b| b.is_ascii_hexdigit()) {
return None;
}
let channel = |range: std::ops::Range<usize>| u8::from_str_radix(&hex[range], 16).ok();
Some((channel(0..2)?, channel(2..4)?, channel(4..6)?))
}

/// Pace tint for the provider that drives a single-provider tray icon.
///
/// Opt-in through `menu_bar_color_pace`; on-track and unknown stages keep the
Expand Down
Loading