fix(coach): lock the reasoning select while a change saves

The provider and model selects in the Coach's settings already lock while
a write is in flight; the reasoning select did not, so a second PATCH
could race the first and one change could be lost.
This commit is contained in:
Marc-André Bombeck committed 2026-10-05 08:34:27 +02:00
1 parent d0b680c277
commit 35edc33871
3 files changed
+16

No files matched your search

@@ -107,6 +107,17 @@ describe("<CoachSettingsBody>", () => {
expect(html).not.toMatch(/type="password"/);
});
it("locks the reasoning select while a change is saving", async () => {
// Two writes in flight at once could land out of order and lose one;
// the provider and model selects already lock, the reasoning one too.
const source = (await import("node:fs")).readFileSync(
new URL("../coach-model-picker.tsx", import.meta.url),
"utf8",
);
const field = source.match(/<ReasoningEffortField[\s\S]*?\/>/)?.[0];
expect(field).toMatch(/disabled=\{busy\}/);
});
it("names the data section so a deep link can land on it", () => {
const html = render(<CoachSettingsBody />);
expect(html).toMatch(
@@ -343,6 +343,7 @@ export function CoachModelPicker() {
<ReasoningEffortField
id="coach-quick-reasoning"
value={reasoning.value}
disabled={busy}
onChange={(next) => patchProvider.mutate({ [reasoning.field]: next })}
/>
) : null}
@@ -14,10 +14,13 @@ import type { ReasoningEffort } from "./shared";
export function ReasoningEffortField({
id,
disabled = false,
value,
onChange,
}: {
id: string;
/** Locks the select while a write is in flight. */
disabled?: boolean;
value: ReasoningEffort | null;
onChange: (next: ReasoningEffort | null) => void;
}) {
@@ -28,6 +31,7 @@ export function ReasoningEffortField({
<NativeSelect
id={id}
value={value ?? ""}
disabled={disabled}
onChange={(e) =>
onChange(
e.target.value === "" ? null : (e.target.value as ReasoningEffort),