Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
736ee58
Share one SettingsSnapshot test fixture
Finesssee Oct 10, 2026
2524ffe
Share rate-window test builder
Finesssee Oct 10, 2026
672e8e1
Share provider usage snapshot test builder
Finesssee Oct 10, 2026
59c9434
Share locale and event vi.mock factories
Finesssee Oct 10, 2026
49fe562
Delete unused chart geometry exports
Finesssee Oct 10, 2026
650cb60
Delete unused mergeValidCurrencyRates
Finesssee Oct 10, 2026
efceeba
Delete FALLBACK_CURRENCY_RATES, unused after mergeValidCurrencyRates
Finesssee Oct 10, 2026
146d586
Delete unused MenuSummary and its test
Finesssee Oct 10, 2026
03b5064
Delete unused providerDisplayDetailTitle and its test
Finesssee Oct 10, 2026
eed954b
Delete unused paceCategoryKey and its test
Finesssee Oct 10, 2026
f8efab9
Delete five unused bridge invoke wrappers
Finesssee Oct 10, 2026
8a3f27f
Load provider icons with import.meta.glob and derive registry ids
Finesssee Oct 10, 2026
590cd43
Delete unused locale keys
Finesssee Oct 10, 2026
256d5bb
Add SettingToggle and SettingSelect rows for settings tabs
Finesssee Oct 10, 2026
0ba940d
Share Codex account store hook
Finesssee Oct 10, 2026
7bb8c70
Add useTauriEvent hook for plain event subscriptions
Finesssee Oct 10, 2026
bee303d
Delete dead CSS rules
Finesssee Oct 10, 2026
061c6f4
Delete shadowed CSS declarations
Finesssee Oct 10, 2026
64b9cb2
Merge the duplicate universal CSS rule
Finesssee Oct 10, 2026
41024f4
Delete base CSS declarations overridden by tray rules
Finesssee Oct 10, 2026
7618782
Split styles.css into per-surface stylesheets
Finesssee Oct 10, 2026
ba71725
Split locale keys into topic arrays
Finesssee Oct 10, 2026
d2033dc
Drop the duplicate ProviderSummary declaration
Finesssee Oct 10, 2026
dddff9d
Split bridge DTOs into area files behind the barrel
Finesssee Oct 10, 2026
14ade1c
Split tray panel, menu card and float bar tests by topic
Finesssee Oct 10, 2026
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
33 changes: 31 additions & 2 deletions apps/desktop-tauri/scripts/check-locale-drift.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,39 @@ const tsBlockMatch = tsSrc.match(
if (!tsBlockMatch) {
die(2, "could not locate `export const ALL_LOCALE_KEYS` in keys.ts");
}
// The block holds `"Key"` literals and `...TOPIC_KEYS` spreads of arrays
// imported from i18n/keyGroups/*.ts; resolve both in order.
const tsKeys = [];
const tsKeyRe = /"(\w+)"/g;
while ((m = tsKeyRe.exec(tsBlockMatch[1])) !== null) {
tsKeys.push(m[1]);
const entryRe = /"(\w+)"|\.\.\.([A-Z_]+)/g;
while ((m = entryRe.exec(tsBlockMatch[1])) !== null) {
if (m[1]) {
tsKeys.push(m[1]);
continue;
}
const name = m[2];
const importMatch = tsSrc.match(
new RegExp(`import\\s*\\{\\s*${name}\\s*\\}\\s*from\\s*"(\\./[^"]+)"`),
);
if (!importMatch) {
die(2, `could not resolve the import of ${name} in keys.ts`);
}
let groupSrc;
try {
groupSrc = readFileSync(resolve(dirname(tsPath), `${importMatch[1]}.ts`), "utf8");
} catch (err) {
die(2, `failed to read ${importMatch[1]}.ts: ${err.message}`);
}
const groupMatch = groupSrc.match(
new RegExp(`export const ${name}\\s*=\\s*\\[([\\s\\S]*?)\\]\\s*as const;`),
);
if (!groupMatch) {
die(2, `could not locate \`export const ${name}\` in ${importMatch[1]}.ts`);
}
let k;
while ((k = tsKeyRe.exec(groupMatch[1])) !== null) {
tsKeys.push(k[1]);
}
}
if (tsKeys.length === 0) {
die(2, "parsed zero entries from ALL_LOCALE_KEYS");
Expand Down
82 changes: 4 additions & 78 deletions apps/desktop-tauri/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,89 +59,15 @@ vi.mock("./hooks/useSurfaceSnapshot", () => ({

import App from "./App";
import { buildBundle } from "./test/localeHarness";
import type { BootstrapState, SettingsSnapshot } from "./types/bridge";
import type { BootstrapState } from "./types/bridge";
import { makeSettings } from "./test/fixtures";

function settings(overrides: Partial<SettingsSnapshot> = {}): SettingsSnapshot {
return {
enabledProviders: ["codex", "claude"],
refreshIntervalSecs: 300,
adaptiveRefresh: false,
refreshAllProvidersOnMenuOpen: false,
lowPowerMode: false,
startAtLogin: false,
startMinimized: false,
showNotifications: true,
soundEnabled: true,
notificationSoundTheme: "windows",
notificationSoundPaths: {
predictiveWarning: null,
highUsage: null,
criticalUsage: null,
exhausted: null,
statusIssue: null,
sessionDepleted: null,
sessionRestored: null,
},
highUsageThreshold: 70,
criticalUsageThreshold: 90,
predictivePaceWarningEnabled: false,
credentialExpiryNotificationsEnabled: false,
trayIconMode: "single",
switcherShowsIcons: true,
menuBarShowsHighestUsage: false,
menuBarShowsPercent: false,
menuBarColorPace: false,
showAsUsed: true,
showAllTokenAccountsInMenu: false,
enableAnimations: true,
resetTimeRelative: true,
showResetWhenExhausted: false,
menuBarDisplayMode: "detailed",
overviewLayout: "detailed",
hidePersonalInfo: false,
updateChannel: "stable",
autoDownloadUpdates: false,
installUpdatesOnQuit: false,
globalShortcut: "Ctrl+Shift+U",
switcherShortcuts: {},
codexCustomSessionsDirs: [],
uiLanguage: "english",
// "dark" (not "auto") so useTheme's effect short-circuits before ever
// touching window.matchMedia, which jsdom doesn't implement here.
theme: "dark",
windowScalePercent: 125,
trayScalePercent: 100,
trayPanelAlwaysOnTop: false,
powertoysStatusPipeEnabled: false,
claudeAvoidKeychainPrompts: false,
codexSparkUsageVisible: true,
disableKeychainAccess: false,
providerMetrics: {},
floatBarEnabled: false,
floatBarOpacity: 80,
floatBarScale: 100,
floatBarOrientation: "horizontal",
floatBarStyle: "floating",
floatBarClickThrough: false,
floatBarProviderIds: [],
floatBarDarkText: false,
floatBarShowResetInline: false,
floatBarShowCost: false,
claudeDailyRoutinesUsageVisible: true,
claudeAllowReadingClaudeCodeCredentials: false,
alibabaTokenPlanRegion: "cn",
weeklyProgressWorkDays: null,
costSummaryDisplayStyle: "compact",
providerAccentColors: {},
...overrides,
};
}

function bootstrap(): BootstrapState {
return {
contractVersion: "v1",
providers: [],
settings: settings(),
settings: makeSettings(),
};
}

Expand All @@ -151,7 +77,7 @@ describe("App window-label routing", () => {
webviewWindowMocks.label = "main";
surfaceMocks.snapshot = { mode: "hidden", target: { kind: "summary" } };
tauriMocks.getBootstrapState.mockResolvedValue(bootstrap());
tauriMocks.getSettingsSnapshot.mockResolvedValue(settings());
tauriMocks.getSettingsSnapshot.mockResolvedValue(makeSettings());
tauriMocks.checkForUpdates.mockResolvedValue({
status: "idle",
version: null,
Expand Down
4 changes: 1 addition & 3 deletions apps/desktop-tauri/src/components/AgentSessions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ vi.mock("../lib/tauri", () => ({
focusAgentSession: api.focusAgentSession,
}));
// t(key) returns the key, so provider labels assert against locale-key names.
vi.mock("../hooks/useLocale", () => ({
useLocale: () => ({ t: (key: string) => key, language: "english" }),
}));
vi.mock("../hooks/useLocale", () => import("../test/mocks/locale"));

import AgentSessions from "./AgentSessions";
import type { AgentSession } from "../types/bridge";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { describe, expect, it, vi } from "vitest";
import type { ClaudeAccount } from "../types/bridge";
import ClaudeAccountUsage from "./ClaudeAccountUsage";

vi.mock("../hooks/useLocale", () => ({ useLocale: () => ({ t: (key: string) => key }) }));
vi.mock("../hooks/useLocale", () => import("../test/mocks/locale"));
const t = (key: string) => key;
const account: ClaudeAccount = {
id: "one:org", email: "one@example.test", organization: null, plan: "max", isActive: false, isSaved: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const mocks = vi.hoisted(() => {
});
vi.mock("../lib/tauri", () => mocks);
vi.mock("@tauri-apps/api/event", () => ({ listen: mocks.listen }));
vi.mock("../hooks/useLocale", () => ({ useLocale: () => ({ t: (key: string) => key }) }));
vi.mock("../hooks/useLocale", () => import("../test/mocks/locale"));
import ClaudeAccountsMenu from "./ClaudeAccountsMenu";

const first: ClaudeAccount = { id: "first:org", email: "first@example.com", organization: "Personal", plan: "max", isActive: true, isSaved: true };
Expand Down
63 changes: 14 additions & 49 deletions apps/desktop-tauri/src/components/CodexAccountsMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { useCallback, useEffect, useState } from "react";
import { listen } from "@tauri-apps/api/event";
import type {
CodexAccount,
CodexAccountsStateBridge,
CodexAccountUsageSnapshot,
} from "../types/bridge";
import { useEffect, useState } from "react";
import type { CodexAccount, CodexAccountUsageSnapshot } from "../types/bridge";
import { useCodexAccountsState } from "../hooks/useCodexAccountsState";
import { useLocale } from "../hooks/useLocale";
import { useFormattedResetTime } from "../hooks/useFormattedResetTime";
import ProviderAccountsMenu from "./ProviderAccountsMenu";
Expand All @@ -13,7 +9,6 @@ import {
codexAccountAdd,
codexAccountReauthenticate,
codexAccountSwitch,
getCodexAccountsState,
refreshProviders,
} from "../lib/tauri";

Expand All @@ -32,56 +27,26 @@ export default function CodexAccountsMenu({
onLayoutChange?: () => void;
}) {
const { t } = useLocale();
const [accounts, setAccounts] = useState<CodexAccount[]>([]);
const [snapshots, setSnapshots] = useState<
Record<string, CodexAccountUsageSnapshot>
>({});
const [displayNames, setDisplayNames] = useState<Record<string, string>>({});
const [accountOrdinals, setAccountOrdinals] = useState<Record<string, number>>({});
const [accountNeedsAuthentication, setAccountNeedsAuthentication] = useState<Record<string, boolean>>({});
const [loading, setLoading] = useState(true);
const {
accounts,
snapshots,
displayNames,
accountOrdinals,
needsAuthentication: accountNeedsAuthentication,
loading,
error,
setError,
load,
} = useCodexAccountsState();
const [pending, setPending] = useState(false);
const [signingIn, setSigningIn] = useState(false);
const busy = loading || pending;
const [error, setError] = useState<string | null>(null);
const [switched, setSwitched] = useState(false);

const load = useCallback(async () => {
setLoading(true);
setError(null);
try {
const next: CodexAccountsStateBridge = await getCodexAccountsState();
setAccounts(next.accounts);
setDisplayNames(next.displayNames ?? {});
setAccountOrdinals(next.accountOrdinals);
setSnapshots(next.snapshots);
setAccountNeedsAuthentication(next.needsAuthentication ?? {});
} catch (err: unknown) {
setError(err instanceof Error ? err.message : String(err));
} finally {
setLoading(false);
}
}, []);

useEffect(() => {
void load();
}, [load]);

useEffect(() => {
onLayoutChange?.();
}, [accounts, snapshots, error, pending, switched, onLayoutChange]);

useEffect(() => {
let cancelled = false;
const unlistenPromise = listen("codex-accounts-updated", () => {
if (!cancelled) void load();
});
return () => {
cancelled = true;
void unlistenPromise.then((fn) => fn());
};
}, [load]);

const run = async (action: () => Promise<unknown>, login = false) => {
setSwitched(false);
setSigningIn(login);
Expand Down
78 changes: 78 additions & 0 deletions apps/desktop-tauri/src/components/FormControls.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type React from "react";
import { createContext, useContext, useId } from "react";
import type { SettingsSnapshot, SettingsUpdate } from "../types/bridge";

// A Field's label and description ids, so the control inside it gets an
// accessible name without every call site passing `ariaLabel`.
Expand Down Expand Up @@ -154,3 +155,80 @@ export function Field({
</div>
);
}

// ── settings-bound rows ──────────────────────────────────────────────

export interface SettingsControl {
settings: SettingsSnapshot;
set: (patch: SettingsUpdate) => void;
saving: boolean;
}

type SettingKey<T> = {
[K in keyof SettingsUpdate & keyof SettingsSnapshot]-?: NonNullable<SettingsUpdate[K]> extends T
? K
: never;
}[keyof SettingsUpdate & keyof SettingsSnapshot];

/** A leading Field + Toggle that writes one boolean setting; an unset value reads as off. */
export function SettingToggle({
ctl,
field,
label,
description,
checked,
disabled,
ariaLabel,
}: {
ctl: SettingsControl;
field: SettingKey<boolean>;
label: string;
description?: string;
checked?: boolean;
disabled?: boolean;
ariaLabel?: string;
}) {
return (
<Field label={label} description={description} leading>
<Toggle
checked={checked ?? ctl.settings[field] ?? false}
ariaLabel={ariaLabel}
disabled={disabled ?? ctl.saving}
onChange={(v) => ctl.set({ [field]: v })}
/>
</Field>
);
}

/** A Field + Select that writes one string setting; an unset value selects "". */
export function SettingSelect({
ctl,
field,
label,
description,
options,
value,
disabled,
ariaLabel,
}: {
ctl: SettingsControl;
field: SettingKey<string>;
label: string;
description?: string;
options: { value: string; label: string }[];
value?: string;
disabled?: boolean;
ariaLabel?: string;
}) {
return (
<Field label={label} description={description}>
<Select
value={value ?? ctl.settings[field] ?? ""}
disabled={disabled ?? ctl.saving}
ariaLabel={ariaLabel}
options={options}
onChange={(v) => ctl.set({ [field]: v })}
/>
</Field>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ vi.mock("@tauri-apps/api/event", () => ({
return Promise.resolve(() => mocks.listeners.delete(event));
},
}));
vi.mock("../hooks/useLocale", () => ({ useLocale: () => ({ t: (key: string) => key }) }));
vi.mock("../hooks/useLocale", () => import("../test/mocks/locale"));
import GrokAccountsMenu from "./GrokAccountsMenu";

const first: GrokAccount = {
Expand Down
Loading