-
Notifications
You must be signed in to change notification settings - Fork 492
fix(gui): gate Codex pool writes until hydrated and ignore stale strategy polls #739
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| /** 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 trimmed = raw.trim(); | ||
| const parsed = trimmed === "" ? NaN : Number(trimmed); | ||
| 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)); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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", | ||
|
|
@@ -24,7 +27,8 @@ export interface AccountPoolStrategyControlsProps { | |
| strategyLabelHidden?: boolean; | ||
| onStrategyChange(strategy: AccountPoolStrategy): void; | ||
| onStickyDraftChange(value: string): void; | ||
| onStickyCommit(): void; | ||
| /** Optional draft overrides React state when steppers commit in the same tick as a draft change. */ | ||
| onStickyCommit(nextDraft?: string): void; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -42,57 +46,73 @@ export default function AccountPoolStrategyControls({ | |
| onStickyCommit, | ||
| }: AccountPoolStrategyControlsProps) { | ||
| const t = useT(); | ||
| const strategyOptions = ACCOUNT_POOL_STRATEGIES.map((value) => ({ | ||
| value, | ||
| label: t(STRATEGY_LABEL_KEYS[value]), | ||
| })); | ||
|
|
||
| return ( | ||
| <div style={{ marginTop: 12 }}> | ||
| <label className="field" style={{ display: "block" }} htmlFor={strategySelectId}> | ||
| <span className={strategyLabelHidden ? "sr-only" : "field-label"}> | ||
| <div className="account-pool-strategy-controls"> | ||
| <div className="field"> | ||
| <span | ||
| className={strategyLabelHidden ? "sr-only" : "field-label"} | ||
| id={`${strategySelectId}-label`} | ||
| > | ||
| {t("accountPool.strategy")} | ||
| </span> | ||
| <select | ||
| <Select | ||
| id={strategySelectId} | ||
| className="input" | ||
| value={strategy} | ||
| options={strategyOptions} | ||
| disabled={disabled} | ||
| aria-label={t("accountPool.strategy")} | ||
| onChange={(event) => { | ||
| onStrategyChange(event.target.value as AccountPoolStrategy); | ||
| }} | ||
| > | ||
| {ACCOUNT_POOL_STRATEGIES.map((value) => ( | ||
| <option key={value} value={value}> | ||
| {t(STRATEGY_LABEL_KEYS[value])} | ||
| </option> | ||
| ))} | ||
| </select> | ||
| </label> | ||
| <div className="card-sub" style={{ marginTop: 4 }}> | ||
| {t("accountPool.strategyHint")} | ||
| label={t("accountPool.strategy")} | ||
| style={{ width: "100%", display: "block" }} | ||
| onChange={(next) => onStrategyChange(next as AccountPoolStrategy)} | ||
| /> | ||
| </div> | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| <div className="card-sub">{t("accountPool.strategyHint")}</div> | ||
| {strategy === "round-robin" && ( | ||
| <label className="field" style={{ display: "block", marginTop: 12 }} htmlFor={stickyInputId}> | ||
| <label className="field" htmlFor={stickyInputId}> | ||
| <span className="field-label">{t("accountPool.stickyLimit")}</span> | ||
| <input | ||
| id={stickyInputId} | ||
| className="input mono" | ||
| type="number" | ||
| min={1} | ||
| max={100} | ||
| step={1} | ||
| inputMode="numeric" | ||
| value={stickyDraft} | ||
| disabled={disabled} | ||
| aria-label={t("accountPool.stickyLimitAria")} | ||
| onChange={(event) => onStickyDraftChange(event.target.value)} | ||
| onBlur={() => onStickyCommit()} | ||
| onKeyDown={(event) => { | ||
| if (event.nativeEvent.isComposing || disabled) return; | ||
| if (event.key === "Enter") { | ||
| event.preventDefault(); | ||
| onStickyCommit(); | ||
| } | ||
| }} | ||
| /> | ||
| <div className="card-sub" style={{ marginTop: 4 }}>{t("accountPool.stickyLimitHelp")}</div> | ||
| <span className="codex-auto-switch-input-wrap"> | ||
| <input | ||
| id={stickyInputId} | ||
| className="input mono codex-auto-switch-input" | ||
| type="number" | ||
| min={1} | ||
| max={100} | ||
| step={1} | ||
| inputMode="numeric" | ||
| value={stickyDraft} | ||
| disabled={disabled} | ||
| aria-label={t("accountPool.stickyLimitAria")} | ||
| onChange={(event) => onStickyDraftChange(event.target.value)} | ||
| onBlur={() => onStickyCommit()} | ||
| onKeyDown={(event) => { | ||
| if (event.nativeEvent.isComposing || disabled) return; | ||
| if (event.key === "Enter") { | ||
| event.preventDefault(); | ||
| onStickyCommit(); | ||
| } | ||
| }} | ||
| /> | ||
| <NumberStepper | ||
| disabled={disabled} | ||
| incrementLabel={t("accountPool.stickyLimitInc")} | ||
| decrementLabel={t("accountPool.stickyLimitDec")} | ||
| onIncrement={() => { | ||
| const next = clampNumberDraft(stickyDraft, 1, 1, 100); | ||
| onStickyDraftChange(next); | ||
| onStickyCommit(next); | ||
| }} | ||
| onDecrement={() => { | ||
| const next = clampNumberDraft(stickyDraft, -1, 1, 100); | ||
| onStickyDraftChange(next); | ||
| onStickyCommit(next); | ||
| }} | ||
| /> | ||
|
Comment on lines
+78
to
+113
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win Unify the "blocked until hydrated/saving" gating mechanism for numeric stepper inputs. Both files add near-identical numeric input +
📍 Affects 2 files
🤖 Prompt for AI Agents |
||
| </span> | ||
| <div className="card-sub">{t("accountPool.stickyLimitHelp")}</div> | ||
| </label> | ||
| )} | ||
| </div> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For every GUI build, this passes an unsupported
idprop because the existingSelectsignature has noidfield, causing a TypeScript error. Even if transpiled without checking,Selectnever forwards the ID to its trigger, so the declared label association and ID-based consumers cannot find this control; extendSelectto accept and apply the ID or avoid relying on it.AGENTS.md reference: gui/AGENTS.md:L44-L50
Useful? React with 👍 / 👎.