From 3b5c9a76e83fc10982700c9c820f79c03d11ec0c Mon Sep 17 00:00:00 2001 From: Ammar Date: Wed, 10 Dec 2025 00:08:26 -0600 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=A4=96=20fix:=20provider=20settings?= =?UTF-8?q?=20subscription=20race=20condition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The onConfigChanged subscription handler was dropping notifications when no listener was actively waiting for them. This caused provider settings updates to not propagate reliably until window reload. The fix adds a pendingNotification flag to queue notifications that arrive before the iterator enters its waiting state, ensuring all config changes are delivered to subscribers. _Generated with mux_ --- src/node/orpc/router.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/node/orpc/router.ts b/src/node/orpc/router.ts index b09501ee9c..b466fcd0a9 100644 --- a/src/node/orpc/router.ts +++ b/src/node/orpc/router.ts @@ -94,14 +94,19 @@ export const router = (authToken?: string) => { .output(schemas.providers.onConfigChanged.output) .handler(async function* ({ context }) { let resolveNext: (() => void) | null = null; + let pendingNotification = false; let ended = false; const push = () => { if (ended) return; if (resolveNext) { + // Listener is waiting - wake it up const resolve = resolveNext; resolveNext = null; resolve(); + } else { + // No listener waiting yet - queue the notification + pendingNotification = true; } }; @@ -109,6 +114,13 @@ export const router = (authToken?: string) => { try { while (!ended) { + // If notification arrived before we started waiting, yield immediately + if (pendingNotification) { + pendingNotification = false; + yield undefined; + continue; + } + // Wait for next notification await new Promise((resolve) => { resolveNext = resolve; }); From 4e43cb5d7a8bf884ffc162834e704a4cbcc44e65 Mon Sep 17 00:00:00 2001 From: Ammar Date: Wed, 10 Dec 2025 09:48:53 -0600 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=A4=96=20test:=20add=20E2E=20test=20f?= =?UTF-8?q?or=20provider=20settings=20propagation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verifies that provider settings updates propagate to the UI without requiring a window reload. The test: 1. Opens settings and sets an OpenAI API key 2. Verifies the masked value appears 3. Closes and reopens settings 4. Confirms the key is still shown as set _Generated with mux_ --- tests/e2e/scenarios/settings.spec.ts | 52 ++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/e2e/scenarios/settings.spec.ts b/tests/e2e/scenarios/settings.spec.ts index 7aaefed7b3..959ed0afbc 100644 --- a/tests/e2e/scenarios/settings.spec.ts +++ b/tests/e2e/scenarios/settings.spec.ts @@ -117,4 +117,56 @@ test.describe("Settings Modal", () => { await expect(page.getByPlaceholder(/model-id/i)).toBeVisible(); await expect(page.getByRole("button", { name: /^Add$/i })).toBeVisible(); }); + + test("provider settings updates propagate without reload", async ({ ui, page }) => { + await ui.projects.openFirstWorkspace(); + await ui.settings.open(); + await ui.settings.selectSection("Providers"); + + // Expand OpenAI provider - use the specific button with OpenAI icon + const openaiButton = page + .getByRole("button", { name: /OpenAI/i }) + .filter({ has: page.getByText("OpenAI icon") }); + await expect(openaiButton).toBeVisible(); + await openaiButton.click(); + + // Wait for the provider section to expand - API Key label should be visible + await expect(page.getByText("API Key", { exact: true })).toBeVisible(); + + // Verify API key is not set initially (shows "Not set") + await expect(page.getByText("Not set").first()).toBeVisible(); + + // Click "Set" to enter edit mode - it's a text link, not a button role + const setLink = page.getByText("Set", { exact: true }).first(); + await setLink.click(); + + // The password input should appear with autofocus + const apiKeyInput = page.locator('input[type="password"]'); + await expect(apiKeyInput).toBeVisible(); + await apiKeyInput.fill("sk-test-key-12345"); + + // Save by pressing Enter (the input has onKeyDown handler for Enter) + await page.keyboard.press("Enter"); + + // Verify the field now shows as set (masked value) + await expect(page.getByText("••••••••")).toBeVisible(); + + // Close settings + await ui.settings.close(); + + // Re-open settings and verify the change persisted without reload + await ui.settings.open(); + await ui.settings.selectSection("Providers"); + + // Expand OpenAI again + await openaiButton.click(); + await expect(page.getByText("API Key", { exact: true })).toBeVisible(); + + // The API key should still show as set + await expect(page.getByText("••••••••")).toBeVisible(); + + // The provider should show as configured (green indicator dot) + const configuredIndicator = openaiButton.locator(".bg-green-500"); + await expect(configuredIndicator).toBeVisible(); + }); });