fix(ui): preserve session picker on empty search blur (#87682)

Summary:
- The PR changes the Control UI chat session picker blur handler to skip empty-query search application and adds a regression test that picker options remain clickable after an empty search blur.
- PR surface: Source +4, Tests +52. Total +56 across 2 files.
- Reproducibility: yes. The issue steps, before recording, and current-main source path all point to the same  ... r clearing picker state before click delivery; I did not rerun a live browser repro in this read-only pass.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(ui): preserve session picker on empty search blur

Validation:
- ClawSweeper review passed for head bb14687756.
- Required merge gates passed before the squash merge.

Prepared head SHA: bb14687756
Review: https://github.com/openclaw/openclaw/pull/87682#issuecomment-4565441074

Co-authored-by: Ryan Weng <14496969+ryan4559@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
This commit is contained in:
clawsweeper[bot]
2026-05-28 16:34:50 +00:00
committed by GitHub
co-authored by Ryan Weng Claude Opus 4.6 clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> takhoffman
parent 0d66710539
commit e9655b9fdc
2 changed files with 57 additions and 1 deletions
+5 -1
View File
@@ -594,7 +594,11 @@ function renderChatSessionPickerPopover(
void applyChatSessionPickerSearch(state);
}
}}
@blur=${() => void applyChatSessionPickerSearch(state)}
@blur=${() => {
if (normalizeOptionalString(state.chatSessionPickerQuery)) {
void applyChatSessionPickerSearch(state);
}
}}
/>
</label>
<button
+52
View File
@@ -1463,6 +1463,58 @@ describe("chat session controls", () => {
});
});
it("keeps picker options clickable after blurring an empty search input", async () => {
const { state } = createChatHeaderState();
state.sessionsIncludeGlobal = false;
state.sessionsIncludeUnknown = false;
const rows: GatewaySessionRow[] = [
{ key: "main", kind: "direct", label: "Main", updatedAt: 2 },
{ key: "agent:main:work", kind: "direct", label: "Work", updatedAt: 1 },
];
state.sessionsResult = createSessionsResultFromRows(rows);
const request = vi.fn((method: string) => {
if (method === "sessions.list") {
return Promise.resolve(createSessionsResultFromRows(rows));
}
throw new Error(`Unexpected request: ${method}`);
});
state.client = { request } as unknown as GatewayBrowserClient;
const onSwitchSession = vi.fn();
const container = document.createElement("div");
render(renderChatSessionSelect(state, onSwitchSession), container);
container.querySelector<HTMLButtonElement>('button[data-chat-session-select="true"]')!.click();
await vi.waitFor(() => expect(state.chatSessionPickerResult).not.toBeNull());
render(renderChatSessionSelect(state, onSwitchSession), container);
const pickerResultBefore = state.chatSessionPickerResult;
const requestCountBefore = request.mock.calls.length;
const input = container.querySelector<HTMLInputElement>(
'input[data-chat-session-picker-search="true"]',
);
expect(input!.value).toBe("");
input!.dispatchEvent(new FocusEvent("blur", { bubbles: false }));
render(renderChatSessionSelect(state, onSwitchSession), container);
expect(state.chatSessionPickerResult).toBe(pickerResultBefore);
expect(state.chatSessionPickerAppliedQuery).toBe("");
expect(state.chatSessionPickerOpen).toBe(true);
expect(request).toHaveBeenCalledTimes(requestCountBefore);
const options = container.querySelectorAll<HTMLButtonElement>(
'button[data-chat-session-picker-option="true"]',
);
const target = [...options].find(
(button) => button.dataset.sessionKey && button.dataset.sessionKey !== state.sessionKey,
);
if (!target?.dataset.sessionKey) {
throw new Error("expected another session option");
}
const targetSessionKey = target.dataset.sessionKey;
target.click();
expect(onSwitchSession).toHaveBeenCalledWith(state, targetSessionKey);
});
it("clears applied chat session picker search when the input is cleared", async () => {
const { state } = createChatHeaderState();
state.sessionsIncludeGlobal = false;