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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ between releases do not reach them. Remote-PC runtime bundles are versioned sepa
the terminal's input box. While that box holds a draft the message stays in the list with the
reason, and **Send now** delivers it once the draft is sent or cleared.
([#628](https://github.com/devswha/herdr-web-ui/pull/628))
- The command palette opens on an empty search every time. It no longer shows the previous
search's results for a moment, during which a quick Tab could highlight a different row than the
one it focused. ([#631](https://github.com/devswha/herdr-web-ui/pull/631))

## [0.4.2] - 2026-10-09

Expand Down
19 changes: 18 additions & 1 deletion scripts/ui-regression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,15 +181,32 @@ try {
await palette.waitFor({ state: "hidden" });
assert.equal(await selectedPane(), paneA, "Enter on Close leaves the pane selection alone");

// A reopened palette starts from an empty search in its first render, the DOM its opening commit
// leaves (sampled by the observer before any later task): a render of the last search's results lets
// a Tab that lands before the next one focus a row at an index the full list then gives to another (#608).
await page.evaluate(() => {
const frames: string[] = [];
(window as unknown as { paletteFirstFrame: string[] }).paletteFirstFrame = frames;
new MutationObserver((_, observer) => {
const search = document.querySelector<HTMLInputElement>(".command-palette input[type=search]");
if (!search) return;
frames.push(search.value);
observer.disconnect();
}).observe(document.body, { childList: true, subtree: true });
});
await openPalette();
assert.deepEqual(await page.evaluate(() => (window as unknown as { paletteFirstFrame: string[] }).paletteFirstFrame), [""],
"a reopened palette's first render shows none of the last search's results");
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
const otherPalettePane = palette.locator(".palette-pane").filter({ hasText: "herdr-web-ui-test-browser-b" });
// Walk the actual tab order instead of clicking: pointer hover must not pick the row for us.
for (const deadline = Date.now() + 5_000; ;) {
await page.keyboard.press("Tab");
if (await otherPalettePane.evaluate((button) => document.activeElement === button)) break;
assert.ok(Date.now() < deadline, "Tab reaches the other pane's option");
}
assert.equal(await otherPalettePane.getAttribute("aria-selected"), "true", "the focused option is highlighted");
// bounded: in #608's race the focused row never became the highlighted one, so this still fails there
await until(async () => await otherPalettePane.evaluate((button) =>
document.activeElement === button && button.getAttribute("aria-selected") === "true"), "the focused option is highlighted");
await page.keyboard.press("Enter");
await palette.waitFor({ state: "hidden" });
await until(async () => await selectedPane() === paneB, "Enter on the focused option selects that pane");
Expand Down
4 changes: 3 additions & 1 deletion src/components/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ export function CommandPalette({ open, onClose, snapshot, selectedPaneId, view,
return () => window.removeEventListener("keydown", dismiss, true);
}, [open, onClose]);

useEffect(() => {
// In the opening commit: a passive effect leaves a render of the last search's results until a later
// task, where a quick Tab focuses a row at an index the full list then gives to another row (#608).
useLayoutEffect(() => {
if (!open) return;
setQuery("");
setActiveIndex(0);
Expand Down