From b83bc4f5e54ca91ecffaf70f7e0679784e19d45c Mon Sep 17 00:00:00 2001 From: Tyler South Date: Sun, 23 Aug 2026 14:32:02 -0400 Subject: [PATCH 1/4] Persist settings window geometry --- CHANGELOG.md | 2 +- apps/desktop-tauri/src-tauri/src/main.rs | 3 + .../src-tauri/src/shell/settings_window.rs | 57 +++++++++++++++---- .../src-tauri/src/shell/tests.rs | 45 +++++++++++++++ .../src/surfaces/Settings.test.ts | 8 +++ apps/desktop-tauri/src/surfaces/Settings.tsx | 43 +------------- 6 files changed, 105 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6af902add..c5c7789b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - **A leaked `serve.token` is rotated on Windows, not just Unix.** After SBS-953, a world-readable token was replaced on Unix, but Windows still tightened the DACL and reused the same secret. The ACL is now inspected before tightening; if anyone other than the current user, SYSTEM, or Administrators can read the file, the token is replaced. Closes SBS-1043. ### Fixed +- **The detached Settings window now reopens where you left it.** Its saved size and position are restored and clamped on screen instead of being overwritten by a second frontend resize on every open. Closes #275. - **Loading settings no longer rewrites another install's start-at-login command.** Every `Settings::load` repaired `HKCU\...\Run\Ceiling` whenever the value was not the quoted path of this process. A portable CLI or a second tree therefore replaced the installed desktop's startup entry, and any extra arguments were stripped. Repair now runs only when this process owns that entry — the same intended exe, or a stale `codexbar-cli.exe` / `codexbar-desktop.exe` sibling in the same directory — and leaves custom arguments and other trees alone. Closes SBS-1053. - **MCP `get_status` no longer hides an exhausted Weekly behind a healthy session.** Top-level `remaining_percent` copied only `usage.primary`, so a Claude/Codex 5-hour window with room made the advertised cap-check sink look fine while Weekly was already at 100%. It now uses the same constraining-window ranking as the desktop strip across primary, secondary, and tertiary (exhausted first, then highest used %). Closes SBS-1055. - **Remembered window positions no longer drop a sibling when two surfaces save at once.** `window_geometry.json` was updated with an unlocked read-modify-write, so moving Settings while the float bar or Pop Out also wrote could replace the file with a snapshot that had never seen the other key. Geometry persist now holds the same cross-process state lock as settings and credentials. Closes SBS-1024. @@ -1561,4 +1562,3 @@ First stable release of Ceiling for Windows. - Async off-main log parsing for responsiveness; strict-concurrency build flags enabled. - Packaging + signing/notarization scripts (arm64); build scripts convert `.icon` bundle to `.icns`. - diff --git a/apps/desktop-tauri/src-tauri/src/main.rs b/apps/desktop-tauri/src-tauri/src/main.rs index 2b98b036c..2d21aef7c 100644 --- a/apps/desktop-tauri/src-tauri/src/main.rs +++ b/apps/desktop-tauri/src-tauri/src/main.rs @@ -298,6 +298,9 @@ fn main() { if shell::flyout_window::handle_window_event(window, event) { return; } + if shell::settings_window::handle_window_event(window, event) { + return; + } // Only the main window participates in blur-dismiss and close-to-hide. // The detached settings window uses normal OS close behavior. if window.label() != "main" { diff --git a/apps/desktop-tauri/src-tauri/src/shell/settings_window.rs b/apps/desktop-tauri/src-tauri/src/shell/settings_window.rs index 89ce28573..22df7170a 100644 --- a/apps/desktop-tauri/src-tauri/src/shell/settings_window.rs +++ b/apps/desktop-tauri/src-tauri/src/shell/settings_window.rs @@ -3,6 +3,8 @@ use tauri::{Emitter, Manager, PhysicalPosition, WebviewUrl}; +use crate::surface::SurfaceMode; + const SETTINGS_LABEL: &str = "settings"; const SETTINGS_WIDTH: f64 = 720.0; const SETTINGS_HEIGHT: f64 = 580.0; @@ -22,9 +24,17 @@ pub fn open_or_focus(app: &tauri::AppHandle, tab: &str) -> Result<(), String> { let url = WebviewUrl::App(format!("index.html?window=settings&tab={tab}").into()); + let stored = crate::geometry_store::load(SurfaceMode::Settings); + let width = stored + .and_then(|geometry| geometry.width) + .map_or(SETTINGS_WIDTH, f64::from); + let height = stored + .and_then(|geometry| geometry.height) + .map_or(SETTINGS_HEIGHT, f64::from); + let win = tauri::WebviewWindowBuilder::new(app, SETTINGS_LABEL, url) .title("Ceiling Settings") - .inner_size(SETTINGS_WIDTH, SETTINGS_HEIGHT) + .inner_size(width, height) .decorations(false) .shadow(false) .theme(Some(tauri::Theme::Dark)) @@ -35,22 +45,47 @@ pub fn open_or_focus(app: &tauri::AppHandle, tab: &str) -> Result<(), String> { // Force DWM caption to dark; keep WS_THICKFRAME since window is resizable super::dwm::force_dark_caption_resizable(&win); - // Manually center: Tauri's .center() is unreliable on Windows when - // called from async commands. Compute position from the primary monitor. - if let Ok(Some(monitor)) = win.primary_monitor() { - let pos = monitor.position(); - let size = monitor.size(); - let scale = win.scale_factor().unwrap_or(1.0); - let win_w = (SETTINGS_WIDTH * scale) as i32; - let win_h = (SETTINGS_HEIGHT * scale) as i32; - let x = pos.x + (size.width as i32 - win_w) / 2; - let y = pos.y + (size.height as i32 - win_h) / 2; + if let Some((x, y)) = super::position::default_surface_position(app, SurfaceMode::Settings) { let _ = win.set_position(PhysicalPosition::new(x, y)); } Ok(()) } +/// Persist move and resize events for the detached Settings window. +pub fn handle_window_event(window: &tauri::Window, event: &tauri::WindowEvent) -> bool { + if window.label() != SETTINGS_LABEL { + return false; + } + + if matches!( + event, + tauri::WindowEvent::Moved(_) | tauri::WindowEvent::Resized(_) + ) && !window.is_maximized().unwrap_or(false) + && !window.is_minimized().unwrap_or(false) + && let Ok(position) = window.outer_position() + { + let scale = window.scale_factor().unwrap_or(1.0).max(1.0); + let logical_size = window.outer_size().ok().map(|size| { + ( + (size.width as f64 / scale).round().max(1.0) as u32, + (size.height as f64 / scale).round().max(1.0) as u32, + ) + }); + crate::geometry_store::save( + SurfaceMode::Settings, + crate::geometry_store::StoredGeometry { + x: position.x, + y: position.y, + width: logical_size.map(|size| size.0), + height: logical_size.map(|size| size.1), + }, + ); + } + + true +} + /// Dismiss Settings without exiting CodexBar. /// /// The detached Settings window is hidden instead of closed so Tauri's diff --git a/apps/desktop-tauri/src-tauri/src/shell/tests.rs b/apps/desktop-tauri/src-tauri/src/shell/tests.rs index 43609da65..2daeecb27 100644 --- a/apps/desktop-tauri/src-tauri/src/shell/tests.rs +++ b/apps/desktop-tauri/src-tauri/src/shell/tests.rs @@ -895,6 +895,51 @@ fn remembered_popout_position_clamps_using_stored_size() { assert_eq!(position, Some((392, 292))); } +#[test] +fn remembered_settings_position_clamps_using_stored_size() { + let monitor = MonitorPlacement { + bounds: Rect { + x: 0, + y: 0, + width: 1000, + height: 800, + }, + work_area: Rect { + x: 0, + y: 0, + width: 1000, + height: 760, + }, + scale_factor: 1.0, + }; + let stored = crate::geometry_store::StoredGeometry { + x: 900, + y: 700, + width: Some(720), + height: Some(580), + }; + + let position = + remembered_surface_position_with_monitors(SurfaceMode::Settings, stored, &[monitor], None); + + assert_eq!(position, Some((272, 172))); +} + +#[test] +fn remembered_panel_size_uses_stored_settings_size() { + let stored = crate::geometry_store::StoredGeometry { + x: 0, + y: 0, + width: Some(840), + height: Some(680), + }; + + let size = remembered_panel_size(SurfaceMode::Settings, stored); + + assert_eq!(size.width, 840); + assert_eq!(size.height, 680); +} + #[test] fn remembered_panel_size_uses_stored_popout_size() { let stored = crate::geometry_store::StoredGeometry { diff --git a/apps/desktop-tauri/src/surfaces/Settings.test.ts b/apps/desktop-tauri/src/surfaces/Settings.test.ts index dec41d93e..0d8f6b1cd 100644 --- a/apps/desktop-tauri/src/surfaces/Settings.test.ts +++ b/apps/desktop-tauri/src/surfaces/Settings.test.ts @@ -1,4 +1,6 @@ import { describe, expect, it } from "vitest"; +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; import { TAB_META } from "./Settings"; /** Live Settings shell tabs, in render order. SBS-872. */ @@ -27,4 +29,10 @@ describe("Settings navigation", () => { // to the Rust allowlist and the SettingsTabId union. expect(TAB_META.map((tab) => tab.id)).toEqual([...LIVE_SETTINGS_TABS]); }); + + it("does not overwrite restored window geometry on mount", () => { + const source = readFileSync(resolve(process.cwd(), "src/surfaces/Settings.tsx"), "utf8"); + expect(source).not.toContain("setSize("); + expect(source).not.toContain("setPosition("); + }); }); diff --git a/apps/desktop-tauri/src/surfaces/Settings.tsx b/apps/desktop-tauri/src/surfaces/Settings.tsx index 5974ec77a..10dc6ac7e 100644 --- a/apps/desktop-tauri/src/surfaces/Settings.tsx +++ b/apps/desktop-tauri/src/surfaces/Settings.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useState, type ReactElement, type ReactNode } from "react"; -import { getCurrentWindow, LogicalPosition, LogicalSize } from "@tauri-apps/api/window"; +import { getCurrentWindow } from "@tauri-apps/api/window"; import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"; import type { BootstrapState, @@ -11,7 +11,7 @@ import { useSurfaceTarget } from "../hooks/useSurfaceMode"; import { useLocale } from "../hooks/useLocale"; import { useTabListKeyboard } from "../hooks/useTabListKeyboard"; import type { LocaleKey } from "../i18n/keys"; -import { closeSettingsWindow, getWorkAreaRect, setSurfaceMode } from "../lib/tauri"; +import { closeSettingsWindow, setSurfaceMode } from "../lib/tauri"; import GeneralTab from "./settings/tabs/GeneralTab"; import DisplayTab from "./settings/tabs/DisplayTab"; import AdvancedTab from "./settings/tabs/AdvancedTab"; @@ -117,41 +117,6 @@ function isSettingsTab(value: string): value is SettingsTab { return TAB_META.some((t) => t.id === value); } -const SETTINGS_WINDOW_HEIGHT = 580; -const SETTINGS_WINDOW_WIDTH = 600; - -async function applySettingsWindowSize() { - const workArea = await getWorkAreaRect().catch(() => null); - const screenWidth = window.screen.availWidth || window.innerWidth || SETTINGS_WINDOW_WIDTH; - const screenHeight = window.screen.availHeight || window.innerHeight || SETTINGS_WINDOW_HEIGHT; - const maxWidth = Math.min(workArea?.width ?? screenWidth, screenWidth); - const maxHeight = Math.min(workArea?.height ?? screenHeight, screenHeight); - const width = Math.max( - 360, - Math.min(SETTINGS_WINDOW_WIDTH, maxWidth - 16), - ); - const height = Math.max( - 360, - Math.min(SETTINGS_WINDOW_HEIGHT, maxHeight - 16), - ); - const win = getCurrentWindow(); - await win.setSize(new LogicalSize(width, height)).catch(() => {}); - const screenOrigin = window.screen as Screen & { - availLeft?: number; - availTop?: number; - }; - const left = screenOrigin.availLeft ?? workArea?.x ?? 0; - const top = screenOrigin.availTop ?? workArea?.y ?? 0; - await win - .setPosition( - new LogicalPosition( - left + Math.max(8, Math.round((screenWidth - width) / 2)), - top + Math.max(8, Math.round((screenHeight - height) / 2)), - ), - ) - .catch(() => {}); -} - export default function Settings({ state, initialTab: propTab }: { state: BootstrapState; initialTab?: string }) { const { settings, saving, error, update } = useSettings(state.settings); const { t } = useLocale(); @@ -164,10 +129,6 @@ export default function Settings({ state, initialTab: propTab }: { state: Bootst : "general"; const [activeTab, setActiveTab] = useState(initialTab); - useEffect(() => { - void applySettingsWindowSize(); - }, []); - // Respond to prop-driven tab changes (detached window re-focus events). useEffect(() => { if (propTab && isSettingsTab(propTab)) { From df96448c5952362537be7fef45d3f8c248f99e7e Mon Sep 17 00:00:00 2001 From: Tyler South Date: Sun, 23 Aug 2026 14:38:37 -0400 Subject: [PATCH 2/4] Implement usage all accounts --- CHANGELOG.md | 2 +- docs/CLI.md | 2 + rust/src/cli/usage.rs | 216 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 196 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6af902add..4d4a578d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - **A leaked `serve.token` is rotated on Windows, not just Unix.** After SBS-953, a world-readable token was replaced on Unix, but Windows still tightened the DACL and reused the same secret. The ACL is now inspected before tightening; if anyone other than the current user, SYSTEM, or Administrators can read the file, the token is replaced. Closes SBS-1043. ### Fixed +- **`usage --all-accounts` now fetches every configured Codex and Claude account.** Account fetches run with bounded concurrency, preserve configured order, and report failures independently. Text and JSON identify each configured account while the default output remains unchanged. Fixes #274. - **Loading settings no longer rewrites another install's start-at-login command.** Every `Settings::load` repaired `HKCU\...\Run\Ceiling` whenever the value was not the quoted path of this process. A portable CLI or a second tree therefore replaced the installed desktop's startup entry, and any extra arguments were stripped. Repair now runs only when this process owns that entry — the same intended exe, or a stale `codexbar-cli.exe` / `codexbar-desktop.exe` sibling in the same directory — and leaves custom arguments and other trees alone. Closes SBS-1053. - **MCP `get_status` no longer hides an exhausted Weekly behind a healthy session.** Top-level `remaining_percent` copied only `usage.primary`, so a Claude/Codex 5-hour window with room made the advertised cap-check sink look fine while Weekly was already at 100%. It now uses the same constraining-window ranking as the desktop strip across primary, secondary, and tertiary (exhausted first, then highest used %). Closes SBS-1055. - **Remembered window positions no longer drop a sibling when two surfaces save at once.** `window_geometry.json` was updated with an unlocked read-modify-write, so moving Settings while the float bar or Pop Out also wrote could replace the file with a snapshot that had never seen the other key. Geometry persist now holds the same cross-process state lock as settings and credentials. Closes SBS-1024. @@ -1561,4 +1562,3 @@ First stable release of Ceiling for Windows. - Async off-main log parsing for responsiveness; strict-concurrency build flags enabled. - Packaging + signing/notarization scripts (arm64); build scripts convert `.icon` bundle to `.icns`. - diff --git a/docs/CLI.md b/docs/CLI.md index 336a957c2..c96c4e425 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -48,6 +48,8 @@ Useful options: - `--source ` - data source. - `--brief` - one compact line per provider. +Without `--all-accounts`, JSON keeps its existing one-entry-per-provider shape. With the flag, directory-backed providers emit one entry per configured account and add a `configured_account` object containing its stable `id` and display `label`. Providers without configured account support still emit one unchanged entry. + Examples: ```sh diff --git a/rust/src/cli/usage.rs b/rust/src/cli/usage.rs index 0b4093d56..bb71d0893 100755 --- a/rust/src/cli/usage.rs +++ b/rust/src/cli/usage.rs @@ -1,16 +1,18 @@ //! Usage command implementation use clap::Args; +use futures::stream::{self, StreamExt}; use serde::Serialize; use crate::core::{ - ConfiguredAccounts, CostSnapshot, FetchContext, ProviderFetchResult, ProviderId, RateWindow, - SourceMode, UsagePace, UsageSnapshot, instantiate_provider, + AccountTarget, ConfiguredAccounts, CostSnapshot, FetchContext, ProviderFetchResult, ProviderId, + RateWindow, SourceMode, UsagePace, UsageSnapshot, instantiate_provider, }; use crate::settings::Settings; use crate::status::{ProviderStatus as StatusInfo, StatusLevel, fetch_provider_status}; pub const PROVIDER_ARG_HELP: &str = "Provider to query (for example: codex, claude, gemini, antigravity/agy, nanogpt, deepseek, codebuff, windsurf, all, both)"; +const MAX_CONCURRENT_ACCOUNT_FETCHES: usize = 4; /// Arguments for the usage command #[derive(Args, Debug, Default)] @@ -179,6 +181,7 @@ struct UsageCommand { pretty: bool, ctx: FetchContext, accounts: ConfiguredAccounts, + all_accounts: bool, } impl UsageCommand { @@ -198,6 +201,7 @@ impl UsageCommand { pretty: args.pretty, ctx: build_usage_fetch_context(&args, source_mode), accounts: ConfiguredAccounts::load(), + all_accounts: args.all_accounts, }) } @@ -243,30 +247,73 @@ enum UsageOutput { }, } +#[derive(Clone)] +struct UsageTarget { + provider: ProviderId, + account: Option, +} + +fn usage_targets(command: &UsageCommand) -> Vec { + command + .providers + .iter() + .flat_map(|provider| { + let accounts = command + .all_accounts + .then(|| command.accounts.targets_for(*provider)) + .unwrap_or_default(); + if accounts.is_empty() { + vec![UsageTarget { + provider: *provider, + account: None, + }] + } else { + accounts + .into_iter() + .map(|account| UsageTarget { + provider: *provider, + account: Some(account), + }) + .collect() + } + }) + .collect() +} + async fn collect_usage_output(command: &UsageCommand) -> UsageOutput { + let targets = usage_targets(command); match command.format { OutputFormat::Text => { - let mut sections = Vec::new(); - for provider_id in &command.providers { - sections.push(fetch_provider_text_output(*provider_id, command).await); - } - UsageOutput::Text(sections) + let mut sections = stream::iter(targets.into_iter().enumerate()) + .map(|(index, target)| async move { + (index, fetch_provider_text_output(&target, command).await) + }) + .buffer_unordered(MAX_CONCURRENT_ACCOUNT_FETCHES) + .collect::>() + .await; + sections.sort_by_key(|(index, _)| *index); + UsageOutput::Text(sections.into_iter().map(|(_, section)| section).collect()) } OutputFormat::Json => { - let mut results = Vec::new(); - for provider_id in &command.providers { - results.push(fetch_provider_json_output(*provider_id, command).await); - } + let mut results = stream::iter(targets.into_iter().enumerate()) + .map(|(index, target)| async move { + (index, fetch_provider_json_output(&target, command).await) + }) + .buffer_unordered(MAX_CONCURRENT_ACCOUNT_FETCHES) + .collect::>() + .await; + results.sort_by_key(|(index, _)| *index); UsageOutput::Json { - results, + results: results.into_iter().map(|(_, result)| result).collect(), pretty: command.pretty, } } } } -async fn fetch_provider_text_output(provider_id: ProviderId, command: &UsageCommand) -> String { - match fetch_provider_result(provider_id, command).await { +async fn fetch_provider_text_output(target: &UsageTarget, command: &UsageCommand) -> String { + let provider_id = target.provider; + let output = match fetch_provider_result(target, command).await { Ok((result, status)) => { if command.brief { render_brief_text(provider_id, &result) @@ -275,34 +322,45 @@ async fn fetch_provider_text_output(provider_id: ProviderId, command: &UsageComm } } Err(e) => render_text_error(provider_id, &e.to_string(), command.use_color), - } + }; + annotate_text_account(output, target.account.as_ref(), command.brief) } async fn fetch_provider_json_output( - provider_id: ProviderId, + target: &UsageTarget, command: &UsageCommand, ) -> serde_json::Value { - match fetch_provider_result(provider_id, command).await { + let provider_id = target.provider; + let mut output = match fetch_provider_result(target, command).await { Ok((result, status)) => render_json_result(provider_id, result, status.as_ref()), Err(e) => serde_json::json!({ "provider": provider_id.cli_name(), "error": e.to_string(), }), - } + }; + annotate_json_account(&mut output, target.account.as_ref()); + output } async fn fetch_provider_result( - provider_id: ProviderId, + target: &UsageTarget, command: &UsageCommand, ) -> anyhow::Result<(ProviderFetchResult, Option)> { + let provider_id = target.provider; let provider = instantiate_provider(provider_id); let status_future = command .fetch_status .then(|| fetch_provider_status(provider_id.cli_name())); - let ctx = command - .ctx - .clone() - .for_account(provider_id, &command.accounts); + let ctx = match &target.account { + Some(account) => command + .ctx + .clone() + .pinned_to_account_dir(provider_id, account.config_dir.clone()), + None => command + .ctx + .clone() + .for_account(provider_id, &command.accounts), + }; let result = provider.fetch_usage(&ctx).await?; let status = if let Some(fut) = status_future { fut.await @@ -312,6 +370,28 @@ async fn fetch_provider_result( Ok((result, status)) } +fn annotate_text_account(output: String, account: Option<&AccountTarget>, brief: bool) -> String { + match account { + Some(account) if brief => { + format!("{output}, account {} ({})", account.label, account.id) + } + Some(account) => format!( + "{output}\n Configured account: {} ({})", + account.label, account.id + ), + None => output, + } +} + +fn annotate_json_account(output: &mut serde_json::Value, account: Option<&AccountTarget>) { + if let Some(account) = account { + output["configured_account"] = serde_json::json!({ + "id": account.id, + "label": account.label, + }); + } +} + fn render_text_error(provider_id: ProviderId, error_msg: &str, use_color: bool) -> String { let header = if use_color { format!("\x1b[1m{}\x1b[0m", provider_id.display_name()) @@ -604,11 +684,101 @@ fn render_progress_bar(percent: f64, width: usize, use_color: bool) -> String { #[cfg(test)] mod tests { use super::*; + use crate::core::{ClaudeIdentity, CodexIdentity, DirectoryAccount}; fn fetch_result(usage: UsageSnapshot) -> ProviderFetchResult { ProviderFetchResult::new(usage, "test") } + fn test_command(all_accounts: bool, accounts: ConfiguredAccounts) -> UsageCommand { + UsageCommand { + format: OutputFormat::Json, + providers: vec![ProviderId::Codex, ProviderId::Claude, ProviderId::Gemini], + use_color: false, + brief: false, + fetch_status: false, + pretty: false, + ctx: FetchContext::default(), + accounts, + all_accounts, + } + } + + #[test] + fn all_accounts_expands_supported_providers_only() { + let mut accounts = ConfiguredAccounts::default(); + accounts + .codex + .add_account(DirectoryAccount::::new( + Some("personal".into()), + "/accounts/personal".into(), + )); + accounts + .codex + .add_account(DirectoryAccount::::new( + Some("work".into()), + "/accounts/work".into(), + )); + accounts + .claude + .add_account(DirectoryAccount::::new( + Some("team".into()), + "/accounts/claude-team".into(), + )); + + let targets = usage_targets(&test_command(true, accounts)); + + assert_eq!(targets.len(), 4); + assert_eq!(targets[0].account.as_ref().unwrap().label, "personal"); + assert_eq!(targets[1].account.as_ref().unwrap().label, "work"); + assert_eq!(targets[2].provider, ProviderId::Claude); + assert_eq!(targets[2].account.as_ref().unwrap().label, "team"); + assert_eq!(targets[3].provider, ProviderId::Gemini); + assert!(targets[3].account.is_none()); + } + + #[test] + fn default_usage_keeps_one_active_target_per_provider() { + let mut accounts = ConfiguredAccounts::default(); + accounts + .codex + .add_account(DirectoryAccount::::new( + Some("personal".into()), + "/accounts/personal".into(), + )); + accounts + .codex + .add_account(DirectoryAccount::::new( + Some("work".into()), + "/accounts/work".into(), + )); + + let targets = usage_targets(&test_command(false, accounts)); + + assert_eq!(targets.len(), 3); + assert!(targets.iter().all(|target| target.account.is_none())); + } + + #[test] + fn configured_account_is_identified_in_json_and_text_errors() { + let account = AccountTarget { + id: "account-id".into(), + label: "work".into(), + tint: None, + config_dir: "/accounts/work".into(), + }; + let mut json = serde_json::json!({"provider": "codex", "error": "failed"}); + + annotate_json_account(&mut json, Some(&account)); + let text = annotate_text_account("Codex Error: failed".into(), Some(&account), false); + let brief = annotate_text_account("Codex: Session 10%".into(), Some(&account), true); + + assert_eq!(json["configured_account"]["id"], "account-id"); + assert_eq!(json["configured_account"]["label"], "work"); + assert!(text.contains("Configured account: work (account-id)")); + assert_eq!(brief, "Codex: Session 10%, account work (account-id)"); + } + #[test] fn text_rendering_shows_sub_one_percent_usage() { let result = fetch_result(UsageSnapshot::new(RateWindow::new(0.4))); From fc4b9dc9017f060b7734b9f1255eea18be4b4948 Mon Sep 17 00:00:00 2001 From: Tyler South Date: Sun, 23 Aug 2026 14:43:19 -0400 Subject: [PATCH 3/4] Keep settings geometry test browser-compatible --- apps/desktop-tauri/src/surfaces/Settings.test.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/desktop-tauri/src/surfaces/Settings.test.ts b/apps/desktop-tauri/src/surfaces/Settings.test.ts index 0d8f6b1cd..eef01d99c 100644 --- a/apps/desktop-tauri/src/surfaces/Settings.test.ts +++ b/apps/desktop-tauri/src/surfaces/Settings.test.ts @@ -1,7 +1,6 @@ import { describe, expect, it } from "vitest"; -import { readFileSync } from "node:fs"; -import { resolve } from "node:path"; import { TAB_META } from "./Settings"; +import settingsSource from "./Settings.tsx?raw"; /** Live Settings shell tabs, in render order. SBS-872. */ const LIVE_SETTINGS_TABS = [ @@ -31,8 +30,7 @@ describe("Settings navigation", () => { }); it("does not overwrite restored window geometry on mount", () => { - const source = readFileSync(resolve(process.cwd(), "src/surfaces/Settings.tsx"), "utf8"); - expect(source).not.toContain("setSize("); - expect(source).not.toContain("setPosition("); + expect(settingsSource).not.toContain("setSize("); + expect(settingsSource).not.toContain("setPosition("); }); }); From ae194c39a7d32d043a96f892efbc7b765b62734c Mon Sep 17 00:00:00 2001 From: Tyler South Date: Sun, 23 Aug 2026 14:45:27 -0400 Subject: [PATCH 4/4] Satisfy usage clippy check --- rust/src/cli/usage.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/rust/src/cli/usage.rs b/rust/src/cli/usage.rs index bb71d0893..ff1e765ed 100755 --- a/rust/src/cli/usage.rs +++ b/rust/src/cli/usage.rs @@ -258,10 +258,11 @@ fn usage_targets(command: &UsageCommand) -> Vec { .providers .iter() .flat_map(|provider| { - let accounts = command - .all_accounts - .then(|| command.accounts.targets_for(*provider)) - .unwrap_or_default(); + let accounts = if command.all_accounts { + command.accounts.targets_for(*provider) + } else { + Vec::new() + }; if accounts.is_empty() { vec![UsageTarget { provider: *provider,