From c4c98c0e7c42a92830615d8262785509ce59958c Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Thu, 30 Jul 2026 08:42:59 +0200 Subject: [PATCH 1/3] fix(gui): gate Codex pool writes until hydrated and ignore stale strategy polls Keep auto-switch / rotation controls from overwriting server state before /active settles, and drop shared /active reads that started before a PUT. --- gui/src/clamp-draft.ts | 14 ++ gui/src/codex-auto-switch.ts | 8 + .../AccountPoolStrategyControls.tsx | 95 +++++---- gui/src/components/CodexAccountPool.tsx | 179 +++++++++-------- gui/src/components/CodexAutoSwitchSetting.tsx | 185 ++++++++++-------- .../components/CodexPoolStrategySetting.tsx | 125 ++++++++---- gui/src/components/NumberStepper.tsx | 43 ++++ gui/src/components/QuotaBars.tsx | 62 +++++- .../components/codex-account-pool-cards.tsx | 31 ++- .../components/codex-account-pool-helpers.tsx | 34 +++- .../codex-account-pool-main-card.tsx | 116 ++++++++--- gui/src/hooks/useCodexAccountPool.ts | 75 +++++-- gui/src/hooks/useCodexAutoSwitch.ts | 45 +++-- gui/src/i18n/de.ts | 68 ++++++- gui/src/i18n/en.ts | 68 ++++++- gui/src/i18n/ja.ts | 68 ++++++- gui/src/i18n/ko.ts | 68 ++++++- gui/src/i18n/ru.ts | 68 ++++++- gui/src/i18n/zh.ts | 68 ++++++- gui/src/pages/CodexAuth.tsx | 52 +++-- gui/src/styles/provider-quota.css | 8 +- gui/tests/account-pool-strategy.test.tsx | 172 +++++++++++++--- gui/tests/codex-account-auto-switch.test.tsx | 48 ++++- .../codex-auto-switch-controller.test.tsx | 98 +++++++++- src/codex/quota.ts | 78 +++++++- 25 files changed, 1479 insertions(+), 397 deletions(-) create mode 100644 gui/src/clamp-draft.ts create mode 100644 gui/src/components/NumberStepper.tsx diff --git a/gui/src/clamp-draft.ts b/gui/src/clamp-draft.ts new file mode 100644 index 000000000..a7450b5ba --- /dev/null +++ b/gui/src/clamp-draft.ts @@ -0,0 +1,14 @@ +/** Clamp a numeric draft string into [min, max] after applying delta (for NumberStepper). */ +export function clampNumberDraft( + raw: string, + delta: number, + min: number, + max: number, + step = 1, +): string { + const parsed = Number(raw); + const base = Number.isFinite(parsed) ? parsed : min; + const next = Math.min(max, Math.max(min, base + delta)); + // Keep one decimal for GiB-style steps; integers otherwise. + return step < 1 ? String(Math.round(next * 10) / 10) : String(Math.round(next)); +} diff --git a/gui/src/codex-auto-switch.ts b/gui/src/codex-auto-switch.ts index 288d2f5cf..ed7d7168d 100644 --- a/gui/src/codex-auto-switch.ts +++ b/gui/src/codex-auto-switch.ts @@ -39,6 +39,14 @@ export function autoSwitchThresholdReadDisposition( return editing || saving ? "defer" : "apply"; } +/** Accept bare threshold numbers or a full /active payload. */ +export function extractAutoSwitchThresholdPayload(value: unknown): unknown { + if (value && typeof value === "object" && value !== null && "autoSwitchThreshold" in value) { + return (value as { autoSwitchThreshold: unknown }).autoSwitchThreshold; + } + return value; +} + export interface AutoSwitchTogglePlan { threshold: number; lastEnabled: number; diff --git a/gui/src/components/AccountPoolStrategyControls.tsx b/gui/src/components/AccountPoolStrategyControls.tsx index 8db64a0df..3b72b63dd 100644 --- a/gui/src/components/AccountPoolStrategyControls.tsx +++ b/gui/src/components/AccountPoolStrategyControls.tsx @@ -3,6 +3,9 @@ import { ACCOUNT_POOL_STRATEGIES, type AccountPoolStrategy, } from "../account-pool-strategy"; +import { clampNumberDraft } from "../clamp-draft"; +import { NumberStepper } from "./NumberStepper"; +import { Select } from "../ui"; const STRATEGY_LABEL_KEYS = { quota: "accountPool.strategyQuota", @@ -42,57 +45,65 @@ export default function AccountPoolStrategyControls({ onStickyCommit, }: AccountPoolStrategyControlsProps) { const t = useT(); + const strategyOptions = ACCOUNT_POOL_STRATEGIES.map((value) => ({ + value, + label: t(STRATEGY_LABEL_KEYS[value]), + })); + return ( -
-