From dda2df0145b1dcbe9a1d94972ceb106d231dde3e Mon Sep 17 00:00:00 2001 From: Badcuban <108198679+badcuban@users.noreply.github.com> Date: Sun, 2 Aug 2026 18:55:27 -0400 Subject: [PATCH 1/4] Stabilize main CI --- .../Layers/CheckpointReactor.test.ts | 1 + .../Layers/ProviderCommandReactor.test.ts | 1 + .../Layers/SubagentWorktreeFollower.ts | Bin 11929 -> 12321 bytes .../Layers/ProviderSessionReaper.test.ts | 1 + apps/web/src/components/ChatView.browser.tsx | 34 ++++++++++-------- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts b/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts index 1e5cfcce..7a6bb19f 100644 --- a/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts +++ b/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts @@ -152,6 +152,7 @@ function createProviderServiceHarness( }), rollbackConversation, readSubagentTranscript: () => unsupported(), + resolveSubagentWorktree: () => Effect.succeed(null), deleteThread: () => unsupported(), get streamEvents() { return Stream.fromPubSub(runtimeEventPubSub); diff --git a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts b/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts index 29690208..eea8c49b 100644 --- a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts +++ b/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts @@ -412,6 +412,7 @@ describe("ProviderCommandReactor", () => { }, rollbackConversation: () => unsupported(), readSubagentTranscript: () => unsupported(), + resolveSubagentWorktree: () => Effect.succeed(null), deleteThread: () => unsupported(), get streamEvents() { return Stream.fromPubSub(runtimeEventPubSub); diff --git a/apps/server/src/orchestration/Layers/SubagentWorktreeFollower.ts b/apps/server/src/orchestration/Layers/SubagentWorktreeFollower.ts index bbeb3ce89ab9bb10290df0fdca3100d19c8d54e0..76210aac48d5540785e35ddef81cfd1c1b69e92b 100644 GIT binary patch delta 272 zcmYL^u};H442Jiv9Vj3nm!WN>DB0K<`V34+9CvnmBt1v=g$xMs5D^0-Z-B(iyWx~d zc=|2<{{G(|_n!}6>-k&N#SS+!I8I%|9ZQo15n>_~Ox~2jBWeaDV#yWULMQx$K1WU< z=mL<7Z7&Fi-fW5UKdwdB7%DSlr%kP>I$g!IGoq<=3B#Thfw*mXKmpGwi}S|8XIm$< zRxUbP^{sJU+Spxt7 delta 12 TcmZ3OFf(?8y71=pB1O^wBX9)e diff --git a/apps/server/src/provider/Layers/ProviderSessionReaper.test.ts b/apps/server/src/provider/Layers/ProviderSessionReaper.test.ts index c5633eee..a9bb0805 100644 --- a/apps/server/src/provider/Layers/ProviderSessionReaper.test.ts +++ b/apps/server/src/provider/Layers/ProviderSessionReaper.test.ts @@ -233,6 +233,7 @@ describe("ProviderSessionReaper", () => { }, rollbackConversation: () => unsupported(), readSubagentTranscript: () => unsupported(), + resolveSubagentWorktree: () => Effect.succeed(null), deleteThread: () => unsupported(), streamEvents: Stream.empty, }; diff --git a/apps/web/src/components/ChatView.browser.tsx b/apps/web/src/components/ChatView.browser.tsx index 3e96e1e6..126200a4 100644 --- a/apps/web/src/components/ChatView.browser.tsx +++ b/apps/web/src/components/ChatView.browser.tsx @@ -1931,6 +1931,17 @@ async function waitForCommandPaletteInput(placeholder: string): Promise { + const palette = page.getByTestId("command-palette"); + // The palette replaces its action list when the Sources view commits. Wait + // for that view and an attached action so a slow render cannot detach the + // Local folder node between locating and clicking it. + await expect.element(palette.getByText("Sources", { exact: true })).toBeInTheDocument(); + const localFolderAction = palette.getByText("Local folder", { exact: true }); + await expect.element(localFolderAction).toBeInTheDocument(); + await localFolderAction.click(); +} + function getCommandPaletteLegendEntries(): string[] { const footer = document.querySelector('[data-slot="command-footer"]'); if (!footer) { @@ -6912,7 +6923,7 @@ describe("ChatView timeline estimator parity (full app)", () => { await expect.element(palette).toBeInTheDocument(); await palette.getByText("Add project", { exact: true }).click(); - await palette.getByText("Local folder", { exact: true }).click(); + await selectLocalFolderAction(); const browseInput = await waitForCommandPaletteInput(ADD_PROJECT_SUBMENU_PLACEHOLDER); await page.getByPlaceholder(ADD_PROJECT_SUBMENU_PLACEHOLDER).fill("~/Development/"); @@ -7101,7 +7112,7 @@ describe("ChatView timeline estimator parity (full app)", () => { const palette = page.getByTestId("command-palette"); await expect.element(palette).toBeInTheDocument(); - await palette.getByText("Local folder", { exact: true }).click(); + await selectLocalFolderAction(); const browseInput = await waitForCommandPaletteInput(ADD_PROJECT_SUBMENU_PLACEHOLDER); await expect.element(browseInput).toHaveValue("~/"); @@ -7164,7 +7175,7 @@ describe("ChatView timeline estimator parity (full app)", () => { const palette = page.getByTestId("command-palette"); await expect.element(palette).toBeInTheDocument(); - await palette.getByText("Local folder", { exact: true }).click(); + await selectLocalFolderAction(); const browseInput = await waitForCommandPaletteInput(ADD_PROJECT_SUBMENU_PLACEHOLDER); await expect.element(browseInput).toHaveValue("~/Development/"); @@ -7224,7 +7235,7 @@ describe("ChatView timeline estimator parity (full app)", () => { await page.getByTestId("sidebar-add-project-trigger").click(); await expect.element(palette).toBeInTheDocument(); - await palette.getByText("Local folder", { exact: true }).click(); + await selectLocalFolderAction(); const browseInput = await waitForCommandPaletteInput(ADD_PROJECT_SUBMENU_PLACEHOLDER); await page.getByPlaceholder(ADD_PROJECT_SUBMENU_PLACEHOLDER).fill("~/Desktop/fresh-project"); @@ -7304,7 +7315,7 @@ describe("ChatView timeline estimator parity (full app)", () => { await page.getByTestId("sidebar-add-project-trigger").click(); await expect.element(palette).toBeInTheDocument(); - await palette.getByText("Local folder", { exact: true }).click(); + await selectLocalFolderAction(); const browseInput = await waitForCommandPaletteInput(ADD_PROJECT_SUBMENU_PLACEHOLDER); await page.getByPlaceholder(ADD_PROJECT_SUBMENU_PLACEHOLDER).fill("~/Development/codex/"); @@ -7436,14 +7447,7 @@ describe("ChatView timeline estimator parity (full app)", () => { .element(palette.getByText("This device", { exact: true }).first()) .toBeInTheDocument(); await palette.getByText("Staging", { exact: true }).click(); - // The palette re-renders its list when the environment pick commits the - // Sources view. Await the committed view and an attached action before - // clicking, or the click can land on a mid-transition node that detaches - // under it on a slow runner. - await expect.element(palette.getByText("Sources", { exact: true })).toBeInTheDocument(); - const localFolderAction = palette.getByText("Local folder", { exact: true }); - await expect.element(localFolderAction).toBeInTheDocument(); - await localFolderAction.click(); + await selectLocalFolderAction(); const browseInput = await waitForCommandPaletteInput(ADD_PROJECT_SUBMENU_PLACEHOLDER); await expect.element(browseInput).toHaveValue("~/workspaces/"); @@ -7537,7 +7541,7 @@ describe("ChatView timeline estimator parity (full app)", () => { const palette = page.getByTestId("command-palette"); await expect.element(palette).toBeInTheDocument(); - await palette.getByText("Local folder", { exact: true }).click(); + await selectLocalFolderAction(); const browseInput = palette.getByPlaceholder(ADD_PROJECT_SUBMENU_PLACEHOLDER); await browseInput.fill("~/Applications/access"); @@ -7655,7 +7659,7 @@ describe("ChatView timeline estimator parity (full app)", () => { await expect.element(palette).toBeInTheDocument(); await palette.getByText("Add project", { exact: true }).click(); - await palette.getByText("Local folder", { exact: true }).click(); + await selectLocalFolderAction(); const browseInput = await waitForCommandPaletteInput(ADD_PROJECT_SUBMENU_PLACEHOLDER); await page.getByPlaceholder(ADD_PROJECT_SUBMENU_PLACEHOLDER).fill("~/Development/"); From c685a75dc66cb6a6480cf1850dce4b337f4bd549 Mon Sep 17 00:00:00 2001 From: Badcuban <108198679+badcuban@users.noreply.github.com> Date: Sun, 2 Aug 2026 19:12:46 -0400 Subject: [PATCH 2/4] Avoid no-op source view refresh --- apps/web/src/components/CommandPalette.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx index de5c3c7f..5a16bff2 100644 --- a/apps/web/src/components/CommandPalette.tsx +++ b/apps/web/src/components/CommandPalette.tsx @@ -1211,6 +1211,9 @@ function OpenCommandPaletteDialog() { } void refreshSourceControlDiscovery(target).then((discovery) => { + if (!discovery) { + return; + } setViewStack((previousViews) => { const currentTopView = previousViews.at(-1); if (currentTopView?.groups[0]?.value !== `sources:${environmentId}`) { From 2a449bc135392d7ab1d89010334400dc555217c0 Mon Sep 17 00:00:00 2001 From: Badcuban <108198679+badcuban@users.noreply.github.com> Date: Sun, 2 Aug 2026 19:35:13 -0400 Subject: [PATCH 3/4] Stabilize saved environment browser test --- apps/web/src/components/ChatView.browser.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/web/src/components/ChatView.browser.tsx b/apps/web/src/components/ChatView.browser.tsx index 126200a4..b4fc1ce6 100644 --- a/apps/web/src/components/ChatView.browser.tsx +++ b/apps/web/src/components/ChatView.browser.tsx @@ -7414,6 +7414,18 @@ describe("ChatView timeline estimator parity (full app)", () => { createdAt: NOW_ISO, lastConnectedAt: NOW_ISO, }); + // Adding a saved environment wakes the real connection service. Let its + // missing-credential attempt settle before installing the connected + // runtime fixture; otherwise that background patch can replace the + // source-picker rows while the test is clicking one of them. + await vi.waitFor( + () => { + expect( + useSavedEnvironmentRuntimeStore.getState().byId[REMOTE_ENVIRONMENT_ID]?.authState, + ).toBe("requires-auth"); + }, + { timeout: 8_000, interval: 16 }, + ); useSavedEnvironmentRuntimeStore.getState().patch(REMOTE_ENVIRONMENT_ID, { connectionState: "connected", authState: "authenticated", From 6000d4c12b1047bc9852766823399f530bcecdb3 Mon Sep 17 00:00:00 2001 From: Badcuban <108198679+badcuban@users.noreply.github.com> Date: Sun, 2 Aug 2026 19:51:52 -0400 Subject: [PATCH 4/4] Stabilize command palette browser actions --- apps/web/src/components/ChatView.browser.tsx | 37 +++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/apps/web/src/components/ChatView.browser.tsx b/apps/web/src/components/ChatView.browser.tsx index b4fc1ce6..c868dd49 100644 --- a/apps/web/src/components/ChatView.browser.tsx +++ b/apps/web/src/components/ChatView.browser.tsx @@ -1931,15 +1931,34 @@ async function waitForCommandPaletteInput(placeholder: string): Promise { + const action = await waitForElement(() => { + const palette = document.querySelector('[data-testid="command-palette"]'); + if (!palette) return null; + return ( + Array.from(palette.querySelectorAll('[data-slot="command-item"]')).find((item) => + Array.from(item.querySelectorAll("span")).some( + (content) => content.textContent?.trim() === label, + ), + ) ?? null + ); + }, `Command palette action "${label}" did not render.`); + // Dispatch in the same browser task that located the row. Async palette + // refreshes may replace result nodes between Playwright's actionability + // checks even though the action itself remains continuously available. + action.click(); + await waitForLayout(); +} + async function selectLocalFolderAction(): Promise { - const palette = page.getByTestId("command-palette"); - // The palette replaces its action list when the Sources view commits. Wait - // for that view and an attached action so a slow render cannot detach the - // Local folder node between locating and clicking it. - await expect.element(palette.getByText("Sources", { exact: true })).toBeInTheDocument(); - const localFolderAction = palette.getByText("Local folder", { exact: true }); - await expect.element(localFolderAction).toBeInTheDocument(); - await localFolderAction.click(); + await waitForElement( + () => + Array.from(document.querySelectorAll('[data-slot="command-group-label"]')).find( + (label) => label.textContent?.trim() === "Sources", + ) ?? null, + "Command palette Sources view did not render.", + ); + await clickCommandPaletteAction("Local folder"); } function getCommandPaletteLegendEntries(): string[] { @@ -7458,7 +7477,7 @@ describe("ChatView timeline estimator parity (full app)", () => { await expect .element(palette.getByText("This device", { exact: true }).first()) .toBeInTheDocument(); - await palette.getByText("Staging", { exact: true }).click(); + await clickCommandPaletteAction("Staging"); await selectLocalFolderAction(); const browseInput = await waitForCommandPaletteInput(ADD_PROJECT_SUBMENU_PLACEHOLDER);