diff --git a/Cargo.lock b/Cargo.lock index 9a13634079..dd323d5a5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -667,7 +667,6 @@ dependencies = [ "dirs", "fluent-templates", "futures", - "global-hotkey", "iana-time-zone", "image", "keyring", diff --git a/apps/desktop-tauri/src-tauri/src/floatbar/mod.rs b/apps/desktop-tauri/src-tauri/src/floatbar/mod.rs index 6d431cd0dc..ea38db93f2 100644 --- a/apps/desktop-tauri/src-tauri/src/floatbar/mod.rs +++ b/apps/desktop-tauri/src-tauri/src/floatbar/mod.rs @@ -119,6 +119,7 @@ impl SettingsPatch { && self.provider_ids.is_none() && self.dark_text.is_none() && self.show_reset_inline.is_none() + && self.show_cost.is_none() } /// Apply this patch to a mutable `Settings`. Values are clamped and diff --git a/apps/desktop-tauri/src/floatbar/SettingsSection.test.tsx b/apps/desktop-tauri/src/floatbar/SettingsSection.test.tsx new file mode 100644 index 0000000000..73b5bb842c --- /dev/null +++ b/apps/desktop-tauri/src/floatbar/SettingsSection.test.tsx @@ -0,0 +1,30 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import type { SettingsSnapshot } from "../types/bridge"; +import FloatBarSettingsSection from "./SettingsSection"; + +vi.mock("../hooks/useLocale", () => ({ + useLocale: () => ({ t: (key: string) => key }), +})); + +const settings = { + floatBarEnabled: true, + floatBarOpacity: 90, + floatBarScale: 100, + floatBarOrientation: "horizontal", + floatBarStyle: "floating", + floatBarShowCost: false, + floatBarShowResetInline: false, + floatBarDarkText: false, + floatBarClickThrough: false, +} as unknown as SettingsSnapshot; + +describe("FloatBar settings", () => { + it("renders one cost toggle", () => { + render( + , + ); + + expect(screen.getAllByText("FloatBarShowCost")).toHaveLength(1); + }); +}); diff --git a/apps/desktop-tauri/src/i18n/keys.ts b/apps/desktop-tauri/src/i18n/keys.ts index 51e029cf5b..fcea2e03e9 100644 --- a/apps/desktop-tauri/src/i18n/keys.ts +++ b/apps/desktop-tauri/src/i18n/keys.ts @@ -6,6 +6,7 @@ export const ALL_LOCALE_KEYS = [ "TabGeneral", + "TabProviders", "TabNotifications", "TabMenuBar", "TabMenu", diff --git a/apps/desktop-tauri/src/surfaces/Settings.test.ts b/apps/desktop-tauri/src/surfaces/Settings.test.ts new file mode 100644 index 0000000000..4a06728369 --- /dev/null +++ b/apps/desktop-tauri/src/surfaces/Settings.test.ts @@ -0,0 +1,11 @@ +import { describe, expect, it } from "vitest"; +import { TAB_META } from "./Settings"; + +describe("Settings navigation", () => { + it("lists providers separately after general", () => { + expect(TAB_META.slice(0, 2)).toEqual([ + { id: "general", labelKey: "TabGeneral" }, + { id: "providers", labelKey: "TabProviders" }, + ]); + }); +}); diff --git a/apps/desktop-tauri/src/surfaces/Settings.tsx b/apps/desktop-tauri/src/surfaces/Settings.tsx index a1b02221e4..daf9001cb4 100644 --- a/apps/desktop-tauri/src/surfaces/Settings.tsx +++ b/apps/desktop-tauri/src/surfaces/Settings.tsx @@ -52,6 +52,14 @@ const TabIcons: Record = { ), + providers: ( + + + + + + + ), notifications: ( @@ -89,8 +97,9 @@ const TabIcons: Record = { ), }; -const TAB_META: { id: SettingsTab; labelKey: LocaleKey }[] = [ +export const TAB_META: { id: SettingsTab; labelKey: LocaleKey }[] = [ { id: "general", labelKey: "TabGeneral" }, + { id: "providers", labelKey: "TabProviders" }, { id: "notifications", labelKey: "TabNotifications" }, { id: "menuBar", labelKey: "TabMenuBar" }, { id: "menu", labelKey: "TabMenu" }, @@ -108,7 +117,7 @@ const SETTINGS_WINDOW_PROVIDERS_WIDTH = 600; async function applySettingsWindowSize(tab: SettingsTab) { const requestedWidth = - tab === "general" + tab === "providers" ? SETTINGS_WINDOW_PROVIDERS_WIDTH : SETTINGS_WINDOW_DEFAULT_WIDTH; const workArea = await getWorkAreaRect().catch(() => null); @@ -197,7 +206,7 @@ export default function Settings({ state, initialTab: propTab }: { state: Bootst return (
{/* custom title bar (decorations disabled for guaranteed dark theme) */}
@@ -248,17 +257,17 @@ export default function Settings({ state, initialTab: propTab }: { state: Bootst )} {/* tab panels */} -
+
{activeTab === "general" && ( - <> - - - + + )} + {activeTab === "providers" && ( + )} {activeTab === "notifications" && ( diff --git a/apps/desktop-tauri/src/types/bridge.ts b/apps/desktop-tauri/src/types/bridge.ts index ef7efba335..67e0258109 100644 --- a/apps/desktop-tauri/src/types/bridge.ts +++ b/apps/desktop-tauri/src/types/bridge.ts @@ -2,6 +2,7 @@ export type SurfaceMode = "hidden" | "trayPanel" | "popOut" | "settings"; export type VisibleSurfaceMode = Exclude; export type SettingsTabId = | "general" + | "providers" | "notifications" | "menuBar" | "menu" diff --git a/docs/superpowers/specs/2026-07-12-restore-providers-tab-design.md b/docs/superpowers/specs/2026-07-12-restore-providers-tab-design.md new file mode 100644 index 0000000000..b699c790de --- /dev/null +++ b/docs/superpowers/specs/2026-07-12-restore-providers-tab-design.md @@ -0,0 +1,19 @@ +# Restore Providers Tab + +## Goal + +Restore provider management as a top-level Settings tab instead of embedding it below General settings. + +## Design + +- Add **Providers** immediately after **General** in the Settings tab bar. +- General contains only language, system, and automation settings and uses the normal settings-page scroll behavior. +- Providers retains the existing 600px split-pane layout, searchable provider sidebar, provider detail pane, and independent pane scrolling. +- Opening Settings with the `providers` route selects Providers directly. +- Other tabs, persisted settings, and provider behavior remain unchanged. + +## Verification + +- Add a focused Settings test proving General and Providers render as separate tab panels. +- Run frontend tests, type-check, locale parity, and the desktop build. +- Verify General, Providers, and provider-pane scrolling in the rebuilt app with CUA Driver. diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 596e075ee0..8f83bb9eef 100755 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -48,7 +48,6 @@ async-trait = "0.1" futures = "0.3" image = { version = "0.25", default-features = false, features = ["png"] } -global-hotkey = "0.7.0" # SQLite for reading browser cookies rusqlite = { version = "0.32", features = ["bundled"] } diff --git a/rust/src/core/mod.rs b/rust/src/core/mod.rs index d440e93717..b3b3b9b178 100755 --- a/rust/src/core/mod.rs +++ b/rust/src/core/mod.rs @@ -2,7 +2,6 @@ mod cost_pricing; mod credential_migration; -mod credentials; mod http; mod jsonl_scanner; mod models_dev_pricing; @@ -19,7 +18,6 @@ mod widget_snapshot; pub use cost_pricing::*; pub use credential_migration::*; -pub use credentials::*; pub use http::*; pub use jsonl_scanner::*; pub use models_dev_pricing::*; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 0194e7e0d4..7e73d4e504 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -16,7 +16,6 @@ pub mod notifications; pub mod providers; pub mod secure_file; pub mod settings; -pub mod shortcuts; pub mod sound; pub mod status; diff --git a/rust/src/locale.rs b/rust/src/locale.rs index 179330e8e4..4a696e876d 100644 --- a/rust/src/locale.rs +++ b/rust/src/locale.rs @@ -158,6 +158,7 @@ locale_keys! { // Tab names (Preferences) TabGeneral, + TabProviders, TabNotifications, TabMenuBar, TabMenu, diff --git a/rust/src/locale/en-US.ftl b/rust/src/locale/en-US.ftl index 54cc41cb3b..ebf9f7a8ac 100644 --- a/rust/src/locale/en-US.ftl +++ b/rust/src/locale/en-US.ftl @@ -1,4 +1,5 @@ TabGeneral = General +TabProviders = Providers TabNotifications = Notifications TabMenuBar = Menu Bar TabMenu = Menu diff --git a/rust/src/locale/es-MX.ftl b/rust/src/locale/es-MX.ftl index 8b062ab342..5cc203a715 100644 --- a/rust/src/locale/es-MX.ftl +++ b/rust/src/locale/es-MX.ftl @@ -1,4 +1,5 @@ TabGeneral = General +TabProviders = Proveedores TabNotifications = Notifications TabMenuBar = Menu Bar TabMenu = Menu diff --git a/rust/src/locale/ja-JP.ftl b/rust/src/locale/ja-JP.ftl index 47e0551aa1..26c7f1e7f4 100644 --- a/rust/src/locale/ja-JP.ftl +++ b/rust/src/locale/ja-JP.ftl @@ -1,4 +1,5 @@ TabGeneral = 一般 +TabProviders = プロバイダー TabNotifications = Notifications TabMenuBar = Menu Bar TabMenu = Menu diff --git a/rust/src/locale/ko-KR.ftl b/rust/src/locale/ko-KR.ftl index 42223154a3..1518a56011 100644 --- a/rust/src/locale/ko-KR.ftl +++ b/rust/src/locale/ko-KR.ftl @@ -1,4 +1,5 @@ TabGeneral = 일반 +TabProviders = 제공업체 TabNotifications = Notifications TabMenuBar = Menu Bar TabMenu = Menu diff --git a/rust/src/locale/zh-CN.ftl b/rust/src/locale/zh-CN.ftl index bfcf2e70c1..09a651f859 100644 --- a/rust/src/locale/zh-CN.ftl +++ b/rust/src/locale/zh-CN.ftl @@ -1,4 +1,5 @@ TabGeneral = 通用 +TabProviders = 服务商 TabNotifications = Notifications TabMenuBar = Menu Bar TabMenu = Menu diff --git a/rust/src/locale/zh-TW.ftl b/rust/src/locale/zh-TW.ftl index 5f4dbeb1ca..4656e39e7c 100644 --- a/rust/src/locale/zh-TW.ftl +++ b/rust/src/locale/zh-TW.ftl @@ -1,4 +1,5 @@ TabGeneral = 一般 +TabProviders = 提供者 TabNotifications = Notifications TabMenuBar = Menu Bar TabMenu = Menu diff --git a/rust/src/providers/windsurf/mod.rs b/rust/src/providers/windsurf/mod.rs index 8fbbf4a89c..88c9f5d5fb 100644 --- a/rust/src/providers/windsurf/mod.rs +++ b/rust/src/providers/windsurf/mod.rs @@ -272,8 +272,7 @@ fn valid_json_text(text: &str) -> Option { fn window_from_remaining_percent(remaining: f64, reset_unix: Option) -> RateWindow { let resets_at = reset_unix.and_then(|ts| DateTime::::from_timestamp(ts, 0)); - // ponytail: reset countdown is localized at render time from `resets_at`; - // do not bake a language-specific description into the cached snapshot. + // Reset countdowns are localized at render time; keep cached snapshots language-neutral. RateWindow::with_details((100.0 - remaining).clamp(0.0, 100.0), None, resets_at, None) } diff --git a/rust/src/settings/tests.rs b/rust/src/settings/tests.rs index 06f6dc4cdf..6c6cd37156 100644 --- a/rust/src/settings/tests.rs +++ b/rust/src/settings/tests.rs @@ -95,6 +95,7 @@ fn float_bar_defaults_are_safe() { assert!(settings.float_bar_provider_ids.is_empty()); assert!(!settings.float_bar_dark_text); assert!(!settings.float_bar_show_reset_inline); + assert!(!settings.float_bar_show_cost); } #[test] @@ -210,6 +211,7 @@ fn float_bar_settings_round_trip_through_raw() { float_bar_provider_ids: vec!["claude".into(), "codex".into()], float_bar_dark_text: true, float_bar_show_reset_inline: true, + float_bar_show_cost: true, ..Settings::default() }; @@ -224,6 +226,7 @@ fn float_bar_settings_round_trip_through_raw() { assert_eq!(back.float_bar_provider_ids, vec!["claude", "codex"]); assert!(back.float_bar_dark_text); assert!(back.float_bar_show_reset_inline); + assert!(back.float_bar_show_cost); } #[test] diff --git a/rust/src/status/mod.rs b/rust/src/status/mod.rs index 2d98374471..d0a248568e 100755 --- a/rust/src/status/mod.rs +++ b/rust/src/status/mod.rs @@ -3,20 +3,9 @@ //! Fetches operational status from provider status pages #![allow(dead_code)] -#![allow(unused_imports)] - -pub mod indicators; - use serde::{Deserialize, Serialize}; use std::collections::HashMap; -// Re-export indicator types for convenience -pub use indicators::{ - OverlayPosition, ProviderStatus as IndicatorProviderStatus, - StatusLevel as IndicatorStatusLevel, StatusOverlayConfig, StatuspageIncident, - StatuspageResponse, StatuspageStatus, -}; - /// Status level for a provider #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "lowercase")]