Skip to content

Commit e5e2dd6

Browse files
authored
Hold the add-project intent until an environment exists (#124)
Opening add-project is a one-shot intent, and it was consumed the moment the palette mounted, against whatever environment data had hydrated by then. A click during startup landed on an empty list: the flow dead-ended with guidance meant for genuinely environment-less sessions, and the arriving environments a moment later could not revive it. This is also why the palette browser suites failed most local runs. The intent now waits for the options to exist (the effect re-runs when they arrive; closing the palette still cancels), while hosted-static tabs keep their immediate pairing guidance. Also teaches the suite's waitFor helpers to report what the palette actually rendered at timeout, so the next residual failure diagnoses itself.
1 parent 2281123 commit e5e2dd6

2 files changed

Lines changed: 23 additions & 5 deletions

File tree

apps/web/src/components/ChatView.browser.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,21 +1461,24 @@ async function waitForProductionStyles(): Promise<void> {
14611461

14621462
async function waitForElement<T extends Element>(
14631463
query: () => T | null,
1464-
errorMessage: string,
1464+
errorMessage: string | (() => string),
14651465
): Promise<T> {
1466+
// Lazy messages read the DOM at failure time, so a timeout reports what
1467+
// actually rendered instead of only what was expected.
1468+
const resolveMessage = () => (typeof errorMessage === "string" ? errorMessage : errorMessage());
14661469
let element: T | null = null;
14671470
await vi.waitFor(
14681471
() => {
14691472
element = query();
1470-
expect(element, errorMessage).toBeTruthy();
1473+
expect(element, resolveMessage()).toBeTruthy();
14711474
},
14721475
{
14731476
timeout: 8_000,
14741477
interval: 16,
14751478
},
14761479
);
14771480
if (!element) {
1478-
throw new Error(errorMessage);
1481+
throw new Error(resolveMessage());
14791482
}
14801483
return element;
14811484
}
@@ -1928,7 +1931,12 @@ async function waitForCommandPaletteShortcutLabel(): Promise<void> {
19281931
async function waitForCommandPaletteInput(placeholder: string): Promise<HTMLInputElement> {
19291932
return waitForElement(
19301933
() => document.querySelector(`input[placeholder="${placeholder}"]`) as HTMLInputElement | null,
1931-
`Command palette input with placeholder "${placeholder}" did not render.`,
1934+
() => {
1935+
const palette = document.querySelector('[data-testid="command-palette"]');
1936+
const actual = palette?.querySelector("input")?.getAttribute("placeholder") ?? "<no input>";
1937+
const paletteState = palette ? "open" : "closed";
1938+
return `Command palette input with placeholder "${placeholder}" did not render (palette ${paletteState}, actual placeholder: "${actual}").`;
1939+
},
19321940
);
19331941
}
19341942

apps/web/src/components/CommandPalette.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1312,9 +1312,19 @@ function OpenCommandPaletteDialog() {
13121312
if (openIntent?.kind !== "add-project") {
13131313
return;
13141314
}
1315+
// Environments hydrate a beat after the palette can open, and this
1316+
// intent is one-shot: consuming it against an empty environment list
1317+
// turned an add-project click during startup into a dead end. Hold the
1318+
// intent until an environment exists (closing the palette clears it);
1319+
// this effect re-runs when the options arrive. Hosted-static tabs know
1320+
// immediately that no local environment is coming, so they proceed
1321+
// straight to the pairing guidance.
1322+
if (addProjectEnvironmentOptions.length === 0 && !isHostedStaticApp()) {
1323+
return;
1324+
}
13151325
clearOpenIntent();
13161326
openAddProjectFlow();
1317-
}, [clearOpenIntent, openAddProjectFlow, openIntent]);
1327+
}, [addProjectEnvironmentOptions.length, clearOpenIntent, openAddProjectFlow, openIntent]);
13181328

13191329
// Sidebar "Search all N threads" hands off here: a submenu view scoped to
13201330
// one logical project's threads, searchable with the usual palette ranking.

0 commit comments

Comments
 (0)