Skip to content

Commit 6c55ff0

Browse files
ryan4559claude
andcommitted
fix(ui): preserve session picker on empty search blur
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e5a687f commit 6c55ff0

2 files changed

Lines changed: 91 additions & 1 deletion

File tree

ui/src/ui/chat/session-controls.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,11 @@ function renderChatSessionPickerPopover(
594594
void applyChatSessionPickerSearch(state);
595595
}
596596
}}
597-
@blur=${() => void applyChatSessionPickerSearch(state)}
597+
@blur=${() => {
598+
if (normalizeOptionalString(state.chatSessionPickerQuery)) {
599+
void applyChatSessionPickerSearch(state);
600+
}
601+
}}
598602
/>
599603
</label>
600604
<button

ui/src/ui/views/chat.test.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1433,6 +1433,92 @@ describe("chat session controls", () => {
14331433
});
14341434
});
14351435

1436+
it("does not destroy picker result when blurring an initially empty search input", async () => {
1437+
const { state, request } = createChatHeaderState();
1438+
state.sessionsIncludeGlobal = false;
1439+
state.sessionsIncludeUnknown = false;
1440+
const container = document.createElement("div");
1441+
render(renderChatSessionSelect(state), container);
1442+
1443+
container.querySelector<HTMLButtonElement>('button[data-chat-session-select="true"]')!.click();
1444+
await vi.waitFor(() => expect(state.chatSessionPickerResult).not.toBeNull());
1445+
render(renderChatSessionSelect(state), container);
1446+
const pickerResultBefore = state.chatSessionPickerResult;
1447+
const optionsBefore = container.querySelectorAll(
1448+
'button[data-chat-session-picker-option="true"]',
1449+
);
1450+
expect(optionsBefore.length).toBeGreaterThan(0);
1451+
1452+
const input = container.querySelector<HTMLInputElement>(
1453+
'input[data-chat-session-picker-search="true"]',
1454+
);
1455+
expect(input!.value).toBe("");
1456+
const searchCallsBefore = request.mock.calls.filter(
1457+
([method]) => method === "sessions.list",
1458+
).length;
1459+
1460+
input!.dispatchEvent(new FocusEvent("blur", { bubbles: false }));
1461+
render(renderChatSessionSelect(state), container);
1462+
1463+
expect(state.chatSessionPickerResult).toBe(pickerResultBefore);
1464+
expect(state.chatSessionPickerOpen).toBe(true);
1465+
const optionsAfter = container.querySelectorAll(
1466+
'button[data-chat-session-picker-option="true"]',
1467+
);
1468+
expect(optionsAfter.length).toBe(optionsBefore.length);
1469+
const searchCallsAfter = request.mock.calls.filter(
1470+
([method]) => method === "sessions.list",
1471+
).length;
1472+
expect(searchCallsAfter).toBe(searchCallsBefore);
1473+
});
1474+
1475+
it("session option click still works after blurring an empty search input", async () => {
1476+
const { state } = createChatHeaderState();
1477+
state.sessionsIncludeGlobal = false;
1478+
state.sessionsIncludeUnknown = false;
1479+
state.sessionsResult = createSessionsResultFromRows([
1480+
{ key: "main", kind: "direct", label: "Main", updatedAt: 2 },
1481+
{ key: "agent:main:work", kind: "direct", label: "Work", updatedAt: 1 },
1482+
]);
1483+
const request = vi.fn((method: string) => {
1484+
if (method === "sessions.list") {
1485+
return Promise.resolve(
1486+
createSessionsResultFromRows([
1487+
{ key: "main", kind: "direct", label: "Main", updatedAt: 2 },
1488+
{ key: "agent:main:work", kind: "direct", label: "Work", updatedAt: 1 },
1489+
]),
1490+
);
1491+
}
1492+
throw new Error(`Unexpected request: ${method}`);
1493+
});
1494+
state.client = { request } as unknown as GatewayBrowserClient;
1495+
const onSwitchSession = vi.fn();
1496+
const container = document.createElement("div");
1497+
render(renderChatSessionSelect(state, onSwitchSession), container);
1498+
1499+
container.querySelector<HTMLButtonElement>('button[data-chat-session-select="true"]')!.click();
1500+
await vi.waitFor(() => expect(state.chatSessionPickerResult).not.toBeNull());
1501+
render(renderChatSessionSelect(state, onSwitchSession), container);
1502+
1503+
const input = container.querySelector<HTMLInputElement>(
1504+
'input[data-chat-session-picker-search="true"]',
1505+
);
1506+
expect(input!.value).toBe("");
1507+
input!.dispatchEvent(new FocusEvent("blur", { bubbles: false }));
1508+
render(renderChatSessionSelect(state, onSwitchSession), container);
1509+
1510+
const options = container.querySelectorAll<HTMLButtonElement>(
1511+
'button[data-chat-session-picker-option="true"]',
1512+
);
1513+
const target = [...options].find(
1514+
(button) => button.dataset.sessionKey && button.dataset.sessionKey !== state.sessionKey,
1515+
);
1516+
expect(target).toBeTruthy();
1517+
target!.click();
1518+
1519+
expect(onSwitchSession).toHaveBeenCalledWith(state, target!.dataset.sessionKey);
1520+
});
1521+
14361522
it("clears applied chat session picker search when the input is cleared", async () => {
14371523
const { state } = createChatHeaderState();
14381524
state.sessionsIncludeGlobal = false;

0 commit comments

Comments
 (0)