From 35edc3387172b21eaaa54d79fe0840f73ba645de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Mon, 5 Oct 2026 07:49:28 +0200 Subject: [PATCH] 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. --- .../__tests__/coach-settings-overlay.test.tsx | 11 +++++++++++ .../insights/coach-panel/coach-model-picker.tsx | 1 + src/components/settings/ai/reasoning-effort-field.tsx | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/src/components/insights/coach-panel/__tests__/coach-settings-overlay.test.tsx b/src/components/insights/coach-panel/__tests__/coach-settings-overlay.test.tsx index ae0c02509..bb595f401 100644 --- a/src/components/insights/coach-panel/__tests__/coach-settings-overlay.test.tsx +++ b/src/components/insights/coach-panel/__tests__/coach-settings-overlay.test.tsx @@ -107,6 +107,17 @@ describe("", () => { 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(//)?.[0]; + expect(field).toMatch(/disabled=\{busy\}/); + }); + it("names the data section so a deep link can land on it", () => { const html = render(); expect(html).toMatch( diff --git a/src/components/insights/coach-panel/coach-model-picker.tsx b/src/components/insights/coach-panel/coach-model-picker.tsx index 81bee0d00..bc3bd2424 100644 --- a/src/components/insights/coach-panel/coach-model-picker.tsx +++ b/src/components/insights/coach-panel/coach-model-picker.tsx @@ -343,6 +343,7 @@ export function CoachModelPicker() { patchProvider.mutate({ [reasoning.field]: next })} /> ) : null} diff --git a/src/components/settings/ai/reasoning-effort-field.tsx b/src/components/settings/ai/reasoning-effort-field.tsx index b9bae533f..0092183ef 100644 --- a/src/components/settings/ai/reasoning-effort-field.tsx +++ b/src/components/settings/ai/reasoning-effort-field.tsx @@ -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({ onChange( e.target.value === "" ? null : (e.target.value as ReasoningEffort),