Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ function createProviderServiceHarness(
}),
rollbackConversation,
readSubagentTranscript: () => unsupported(),
resolveSubagentWorktree: () => Effect.succeed(null),
deleteThread: () => unsupported(),
get streamEvents() {
return Stream.fromPubSub(runtimeEventPubSub);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ describe("ProviderCommandReactor", () => {
},
rollbackConversation: () => unsupported(),
readSubagentTranscript: () => unsupported(),
resolveSubagentWorktree: () => Effect.succeed(null),
deleteThread: () => unsupported(),
get streamEvents() {
return Stream.fromPubSub(runtimeEventPubSub);
Expand Down
Binary file modified apps/server/src/orchestration/Layers/SubagentWorktreeFollower.ts
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ describe("ProviderSessionReaper", () => {
},
rollbackConversation: () => unsupported(),
readSubagentTranscript: () => unsupported(),
resolveSubagentWorktree: () => Effect.succeed(null),
deleteThread: () => unsupported(),
streamEvents: Stream.empty,
};
Expand Down
67 changes: 51 additions & 16 deletions apps/web/src/components/ChatView.browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1931,6 +1931,36 @@ async function waitForCommandPaletteInput(placeholder: string): Promise<HTMLInpu
);
}

async function clickCommandPaletteAction(label: string): Promise<void> {
const action = await waitForElement(() => {
const palette = document.querySelector('[data-testid="command-palette"]');
if (!palette) return null;
return (
Array.from(palette.querySelectorAll<HTMLElement>('[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<void> {
await waitForElement(
() =>
Array.from(document.querySelectorAll<HTMLElement>('[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[] {
const footer = document.querySelector('[data-slot="command-footer"]');
if (!footer) {
Expand Down Expand Up @@ -6912,7 +6942,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/");
Expand Down Expand Up @@ -7101,7 +7131,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("~/");
Expand Down Expand Up @@ -7164,7 +7194,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/");
Expand Down Expand Up @@ -7224,7 +7254,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");

Expand Down Expand Up @@ -7304,7 +7334,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/");

Expand Down Expand Up @@ -7403,6 +7433,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",
Expand Down Expand Up @@ -7435,15 +7477,8 @@ 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();
// 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 clickCommandPaletteAction("Staging");
await selectLocalFolderAction();

const browseInput = await waitForCommandPaletteInput(ADD_PROJECT_SUBMENU_PLACEHOLDER);
await expect.element(browseInput).toHaveValue("~/workspaces/");
Expand Down Expand Up @@ -7537,7 +7572,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");

Expand Down Expand Up @@ -7655,7 +7690,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/");
Expand Down
3 changes: 3 additions & 0 deletions apps/web/src/components/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -840,13 +840,13 @@
buildProjectActionItems({
projects,
valuePrefix: "project",
icon: (project) => (
<ProjectFavicon
environmentId={project.environmentId}
cwd={project.cwd}
className={ITEM_ICON_CLASS}
/>
),

Check warning on line 849 in apps/web/src/components/CommandPalette.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Typecheck, Test, Browser Test, Build

react(no-unstable-nested-components)

Do not define components during render.
runProject: openProjectFromSearch,
}),
[openProjectFromSearch, projects],
Expand Down Expand Up @@ -1211,6 +1211,9 @@
}

void refreshSourceControlDiscovery(target).then((discovery) => {
if (!discovery) {
return;
}
setViewStack((previousViews) => {
const currentTopView = previousViews.at(-1);
if (currentTopView?.groups[0]?.value !== `sources:${environmentId}`) {
Expand Down
Loading