Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
64e9963
feat: discover saved drafts through thread lifecycle filters
brsbl Sep 19, 2026
597c10e
test: align lifecycle projection fixtures and contract allowlist
brsbl Sep 19, 2026
75e3507
test: align CLI assertions with existing search and logging contracts
brsbl Sep 19, 2026
759f542
Add sidebar thread lifecycle filters
brsbl Sep 19, 2026
9cdd061
test: supply required identity in draft CLI fixture
brsbl Sep 19, 2026
f7054df
Keep lifecycle filtering nonmodal and await query updates
brsbl Sep 19, 2026
dec4581
Include lifecycle metadata in realtime list fixture
brsbl Sep 19, 2026
7926ec8
Merge commit '9cdd061f528718aba9eb11fbd432b4586b327d05' into bb/sideb…
brsbl Sep 19, 2026
3aa7b99
Add bounded palette thread lifecycle filters
brsbl Sep 19, 2026
38819e2
Order lifecycle-filtered lists by global recency
brsbl Sep 19, 2026
d359076
Merge commit '38819e21f3dc923c773be8a64cf97cbbd57aba33' into bb/sideb…
brsbl Sep 19, 2026
ca2ed62
Merge commit 'd359076a96bacde55b05a2fc1f2b70447eef6c7e' into bb/palet…
brsbl Sep 19, 2026
12262cd
Remove duplicate unavailable Drafts fixture
brsbl Sep 19, 2026
fc608d8
Merge commit '12262cd53a185097577f93e545290f92aa048898' into bb/sideb…
brsbl Sep 19, 2026
a2ff5dc
Merge commit 'fc608d81b1402e9b3c59af589173a8a9489638dd' into bb/palet…
brsbl Sep 19, 2026
57140ea
Move sidebar lifecycle filters into combined header menus
brsbl Sep 20, 2026
910d189
Merge commit '57140ea734553d96809d4524a6ccda228a821895' into bb/palet…
brsbl Sep 20, 2026
a239bdd
Show and reset modified sidebar view settings
brsbl Sep 20, 2026
155ebcd
Preserve sidebar menu state through tooltip trigger
brsbl Sep 20, 2026
657db60
Merge commit '155ebcd1164baf693fd105ce0790a36cbeb7d895' into bb/palet…
brsbl Sep 20, 2026
9c078a3
Defer sidebar view menu contents until opened
brsbl Sep 20, 2026
2f9a2d0
Merge commit '9c078a368965f0daf171f3dcd409e753323fd708' into bb/palet…
brsbl Sep 20, 2026
819e2bd
Render active sidebar hierarchy without a duplicate header
brsbl Sep 20, 2026
8c80292
Merge commit '819e2bdd3e01b65706d183bc4b09d6f2e9f3a03e' into bb/palet…
brsbl Sep 20, 2026
2d6dc18
Integrate archived threads into the sidebar hierarchy
brsbl Sep 20, 2026
184dbc7
Use the sidebar divider token and update grouping guidance
brsbl Sep 20, 2026
5906b72
Align sidebar settings guides with the combined menu
brsbl Sep 20, 2026
e55cc0f
Match sidebar lifecycle test harness to retained sections
brsbl Sep 20, 2026
7af84a4
Merge commit 'e55cc0fdcb1840676c8cd23bea088ba88602331c' into bb/palet…
brsbl Sep 20, 2026
85b3eed
Defer palette recent queries with thread search mode
brsbl Sep 20, 2026
bcdbe00
Quiet sidebar view controls and archived restore action
brsbl Sep 20, 2026
1b21e53
Merge commit 'bcdbe008655b2ea8bec1ed6d7555f1d656c17457' into bb/palet…
brsbl Sep 20, 2026
bc4be26
Preserve Organize controls and simplify lifecycle actions
brsbl Sep 20, 2026
1673a09
Merge commit 'bc4be2677e83a4a8bfbf7a9bc799bb74558c7698' into bb/palet…
brsbl Sep 20, 2026
97842a5
Remove sidebar sort and filter reset actions
brsbl Sep 21, 2026
86396ac
Merge commit '97842a593' into bb/palette-thread-lifecycle-filters-thr…
brsbl Sep 21, 2026
67ca2d9
Keep selected lifecycle filter rows at normal contrast
brsbl Sep 21, 2026
053e7fe
Merge commit '67ca2d9e5beab4dd0d6f133a08a402bbfc696afe' into bb/palet…
brsbl Sep 21, 2026
3205aa3
Index saved queued messages in thread search
brsbl Sep 21, 2026
ff7f214
Treat saved drafts as messages in the ordinary thread list
brsbl Sep 21, 2026
4a04882
Merge branch 'bb/draft-lifecycle-thr_82jtfaq8eq' into bb/sidebar-thre…
brsbl Sep 21, 2026
d56b233
Merge branch 'bb/sidebar-thread-lifecycle-filters-thr_cevdspix6j' int…
brsbl Sep 21, 2026
39ef3ac
Show saved messages in ordinary thread search results
brsbl Sep 21, 2026
421870d
Preserve queue corruption diagnostics and migration fixtures
brsbl Sep 21, 2026
a017003
Merge branch 'bb/draft-lifecycle-thr_82jtfaq8eq' into bb/sidebar-thre…
brsbl Sep 21, 2026
be403f6
Merge branch 'bb/sidebar-thread-lifecycle-filters-thr_cevdspix6j' int…
brsbl Sep 21, 2026
1e9c39b
Rewind queue search rows with the migration fixture
brsbl Sep 21, 2026
c9dac9b
Merge branch 'bb/draft-lifecycle-thr_82jtfaq8eq' into bb/sidebar-thre…
brsbl Sep 21, 2026
5d66e07
Merge branch 'bb/sidebar-thread-lifecycle-filters-thr_cevdspix6j' int…
brsbl Sep 21, 2026
0d40817
Handle pre-search schemas when rewinding migration fixtures
brsbl Sep 21, 2026
7a954da
Merge commit '0d4081763' into bb/sidebar-thread-lifecycle-filters-thr…
brsbl Sep 21, 2026
4d3c673
Merge commit '7a954da5d' into bb/palette-thread-lifecycle-filters-thr…
brsbl Sep 21, 2026
dd21494
Remove obsolete draft expansion label and align regressions
brsbl Sep 21, 2026
0d91874
Merge main and advance saved-message search migration
brsbl Sep 21, 2026
cc64611
Merge updated search foundation into sidebar filters
brsbl Sep 21, 2026
723fe1c
Merge updated sidebar parent into palette filters
brsbl Sep 21, 2026
4b4bc3a
Keep sidebar view settings inside the lazy menu
brsbl Sep 21, 2026
a5d91b1
Merge commit '4b4bc3aaeccbfda7b5d832c6a586b43d60dedaaa' into bb/palet…
brsbl Sep 21, 2026
55c7625
Remove draft thread lifecycle API and classification
brsbl Sep 21, 2026
3b7a667
Remove draft classification from sidebar filtering
brsbl Sep 21, 2026
6af934a
Remove remaining lifecycle fields from sidebar fixtures
brsbl Sep 21, 2026
19fec41
Remove draft lifecycle from palette search and keep explicit recent o…
brsbl Sep 21, 2026
7db1965
Merge commit '6af934a28' into bb/palette-thread-lifecycle-filters-thr…
brsbl Sep 21, 2026
744fd8a
Align palette regressions with Active and Archived filters
brsbl Sep 21, 2026
741fd21
Use Filter for thread filter labels
brsbl Sep 21, 2026
0b2812c
Merge commit '741fd214d' into bb/palette-thread-lifecycle-filters-thr…
brsbl Sep 21, 2026
693824d
Align palette keyboard coverage with Filter label
brsbl Sep 21, 2026
c717d34
Assert native hover hint only in desktop filter menu
brsbl Sep 21, 2026
8f26f24
Merge commit 'c717d3498' into bb/palette-thread-lifecycle-filters-thr…
brsbl Sep 21, 2026
165defe
Match the thread filter icon to existing controls
brsbl Sep 21, 2026
498ad8b
Merge commit '165defedc126efdc0f79a8f02d68d5793ee152a4' into bb/palet…
brsbl Sep 21, 2026
fa8c241
Show All when every thread filter is selected
brsbl Sep 21, 2026
c3995fd
Merge commit 'fa8c241c7eb1d3f9eec5b632a43786a70582b149' into bb/palet…
brsbl Sep 21, 2026
b8d60ad
Cover the combined thread filter label
brsbl Sep 21, 2026
575c44a
Detach sidebar filters from deferred draft search and update main
brsbl Sep 21, 2026
c5420f3
Update palette filters on standalone sidebar parent
brsbl Sep 21, 2026
46a3b3c
Adapt sidebar filter fixture to current section visibility
brsbl Sep 21, 2026
8056b67
Merge commit '46a3b3cbb' into bb/palette-thread-lifecycle-filters-thr…
brsbl Sep 21, 2026
e47acfc
Defer sidebar menu contents until first open
brsbl Sep 21, 2026
1582c7d
Keep menu type imports with module imports
brsbl Sep 21, 2026
866c69a
Merge commit '1582c7d02' into bb/palette-thread-lifecycle-filters-thr…
brsbl Sep 21, 2026
8bfbf64
Load sidebar customization only when requested
brsbl Sep 21, 2026
2c06af0
Merge commit '8bfbf64f8' into bb/palette-thread-lifecycle-filters-thr…
brsbl Sep 21, 2026
fca0570
Hide archived sidebar restore icon on mobile
brsbl Sep 21, 2026
7e99577
Merge commit 'fca05702a' into bb/palette-thread-lifecycle-filters-thr…
brsbl Sep 21, 2026
b1c8496
Use the unarchive icon for archived sidebar row actions
brsbl Sep 21, 2026
5ff58cd
Merge commit 'b1c84962decd33eee520373bc5b93ac2c1829081' into bb/palet…
brsbl Sep 21, 2026
73692f4
Use background focus treatment for sidebar action buttons
brsbl Sep 21, 2026
91020e6
Merge commit '73692f488' into bb/palette-thread-lifecycle-filters-thr…
brsbl Sep 21, 2026
706650e
Restore conventional sidebar keyboard focus
brsbl Sep 21, 2026
01d0ffc
Merge commit '706650ed60db0de8710a512fbd13cd3d31095907' into bb/palet…
brsbl Sep 21, 2026
4ad63bb
Test restored sidebar rows remain visible during unarchive
brsbl Sep 21, 2026
3fc3fdb
Keep restored sidebar threads visible throughout unarchive
brsbl Sep 21, 2026
28ce6f4
Merge commit '3fc3fdbc4ff767e0a0da1ac001a2d414980dc0a0' into bb/palet…
brsbl Sep 21, 2026
c6edbde
Reuse existing archive ordering in palette recents
brsbl Sep 21, 2026
f6f0054
Remove obsolete palette sort cache guard
brsbl Sep 21, 2026
3a4b1fe
Consolidate sidebar filter regression coverage
brsbl Sep 21, 2026
2c593fb
Merge commit '3a4b1fee89bce1681e97b8948bb88c7c0b80a882' into bb/palet…
brsbl Sep 21, 2026
ec7d784
Merge main into sidebar archive filters
brsbl Sep 21, 2026
86222d8
Merge updated sidebar filters into palette filters
brsbl Sep 21, 2026
c2f3cbd
Keep sidebar archive filtering browser-local
brsbl Sep 21, 2026
5de8aa6
Integrate browser-local sidebar and palette filters
brsbl Sep 21, 2026
9255e4c
Remove unused archive action icon override
brsbl Sep 21, 2026
35ffde5
Merge commit '9255e4cce220e1814ba5f41f4ff650f7eaa2aebb' into bb/palet…
brsbl Sep 21, 2026
73b10b3
test(app): consolidate sidebar archive regression coverage
brsbl Sep 21, 2026
5c6000d
Merge commit '73b10b3b38ab769ccc505daa110f22f0d76772d0' into bb/palet…
brsbl Sep 21, 2026
bb2024c
Merge remote-tracking branch 'origin/main' into bb/palette-thread-lif…
brsbl Sep 21, 2026
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
172 changes: 149 additions & 23 deletions apps/app/src/components/commands/CommandPalette.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
} from "@testing-library/react";
import { MemoryRouter, useLocation } from "react-router-dom";
import { createStore, Provider } from "jotai";
import { paletteThreadLifecyclesAtom } from "@/lib/command-palette/palette-preferences";
import { sidebarThreadLifecyclesAtom } from "@/components/sidebar/sidebarCollapsedAtoms";
import { splitLayoutAtom } from "@/lib/split-layout/atoms";
import { MAX_PANES, type SplitLayout } from "@/lib/split-layout";
import { afterEach, describe, expect, it, vi } from "vitest";
Expand All @@ -23,6 +25,7 @@ import {
type ThreadListEntry,
} from "@bb/domain";
import type { ThreadSearchResponse } from "@bb/server-contract";
import type { ThreadArchiveFilter } from "@/lib/thread-lifecycle-filter";
import { CompactViewportOverrideProvider } from "@bb/shared-ui/hooks/use-compact-viewport";
import { AppCommandProvider, useAppCommandHandler } from "./AppCommandProvider";
import {
Expand Down Expand Up @@ -112,6 +115,7 @@ const testState = vi.hoisted(() => ({
}));
const modeState = vi.hoisted(() => ({
activeRecents: [] as ThreadListEntry[],
archivedRecents: [] as ThreadListEntry[],
threadDraftIds: new Set<string>(),
searchResponse: undefined as ThreadSearchResponse | undefined,
recentLoading: false,
Expand Down Expand Up @@ -233,6 +237,14 @@ vi.mock("@/hooks/queries/sidebar-navigation-query", () => ({
}),
}));

vi.mock("@/hooks/queries/palette-thread-queries", () => ({
usePaletteRecentArchivedThreads: () => ({
data: modeState.archivedRecents,
isLoading: false,
isError: false,
}),
}));

vi.mock("@/hooks/queries/thread-queries", async (importOriginal) => {
const actual =
await importOriginal<typeof import("@/hooks/queries/thread-queries")>();
Expand Down Expand Up @@ -315,12 +327,15 @@ function makeThread(
function renderPalette({
compact = false,
layout = null,
lifecycles = ["active"],
}: {
compact?: boolean;
layout?: SplitLayout | null;
lifecycles?: ThreadArchiveFilter[];
} = {}) {
const store = createStore();
store.set(splitLayoutAtom, layout);
store.set(paletteThreadLifecyclesAtom, lifecycles);
const result = render(
<Provider store={store}>
<CompactViewportOverrideProvider isCompactViewport={compact}>
Expand Down Expand Up @@ -394,6 +409,7 @@ afterEach(() => {
testState.showKeyboardHints = true;
testState.plugins.length = 0;
modeState.activeRecents = [];
modeState.archivedRecents = [];
modeState.threadDraftIds.clear();
modeState.searchResponse = undefined;
modeState.recentLoading = false;
Expand Down Expand Up @@ -487,6 +503,15 @@ describe("CommandPalette", () => {
expect(
screen.queryByRole("button", { name: "Open in split" }),
).toBeNull();
if (reason === "compact") {
expect(document.querySelector("kbd")).toBeNull();
const close = screen.getByRole("button", {
name: "Return to commands",
});
act(() => close.focus());
expectText(await screen.findByRole("tooltip"), "Return to commands");
expect(screen.getByRole("tooltip").textContent).not.toContain("Esc");
}
},
);

Expand Down Expand Up @@ -540,7 +565,7 @@ describe("CommandPalette", () => {
],
},
};
renderPalette({ layout: splitLayout });
renderPalette({ layout: splitLayout, lifecycles: ["archived"] });
openThreadSearch();
await screen.findByRole("combobox", { name: "Search threads" });
fireEvent.change(searchField(), { target: { value: "matching" } });
Expand Down Expand Up @@ -990,6 +1015,7 @@ describe("CommandPalette", () => {

it("shows active recents in update order with project metadata and follow-up status", async () => {
modeState.activeRecents = [
makeThread("saved-draft", { status: "pending", updatedAt: 1 }),
makeThread("older", { updatedAt: Date.now() - 100 }),
makeThread("newer", { updatedAt: Date.now(), lastReadAt: Date.now() }),
];
Expand All @@ -1014,6 +1040,7 @@ describe("CommandPalette", () => {
expect(rows.map((row) => row.textContent)).toEqual([
expect.stringContaining("Title newer"),
expect.stringContaining("Title older"),
expect.stringContaining("Title saved-draft"),
]);
expect(screen.queryByRole("button", { name: "Thread scope" })).toBeNull();
expect(
Expand All @@ -1022,9 +1049,9 @@ describe("CommandPalette", () => {
}),
).toBeTruthy();
expect(rows[0].querySelector('[data-icon="Edit"]')).not.toBeNull();
expect(results.querySelectorAll('[data-icon="Folder"]')).toHaveLength(2);
expect(results.querySelectorAll('[data-icon="Folder"]')).toHaveLength(3);
expectClasses(results, "p-1");
expectClasses(within(results).getByText("Recent"), "px-2", "py-1");
expectClasses(within(results).getByText("Active"), "px-2", "py-1");
for (const row of rows) {
const metadata = row.querySelector("[data-palette-thread-metadata]");
expectText(metadata, "Palette project");
Expand All @@ -1033,6 +1060,108 @@ describe("CommandPalette", () => {
}
});

it("keeps the highlighted thread across filter changes and clamps it when removed", async () => {
modeState.activeRecents = [
makeThread("first"),
makeThread("selected", { updatedAt: 1 }),
];
modeState.archivedRecents = [makeThread("archived", { archivedAt: 1 })];
const { store } = renderPalette();
openThreadSearch();
const input = await screen.findByRole("combobox", {
name: "Search threads",
});
fireEvent.keyDown(input, { key: "ArrowDown" });
expectText(selectedOption(), "Title selected");
act(() =>
store.set(paletteThreadLifecyclesAtom, ["archived", "active"]),
);
expect(
screen
.getAllByRole("group")
.map((group) => group.textContent?.split("Title")[0]),
).toEqual(["Active", "Archived"]);
expectText(selectedOption(), "Title selected");
act(() => store.set(paletteThreadLifecyclesAtom, ["archived"]));
expectText(selectedOption(), "Title archived");
expect(input.getAttribute("aria-activedescendant")).toBe(
selectedOption()?.id,
);
expect(store.get(sidebarThreadLifecyclesAtom)).toEqual(["active"]);
});

it("operates the lifecycle filter with the keyboard without selecting a result", async () => {
modeState.activeRecents = [makeThread("active")];
const { store } = renderPalette();
openThreadSearch();
await screen.findByRole("combobox", { name: "Search threads" });
const trigger = screen.getByRole("button", {
name: "Filter: Active",
});
act(() => trigger.focus());
fireEvent.keyDown(trigger, { key: "ArrowDown" });
const archived = await screen.findByRole("menuitemcheckbox", {
name: "Archived",
});
act(() => archived.focus());
fireEvent.keyDown(archived, { key: "Enter" });
expect(store.get(paletteThreadLifecyclesAtom)).toEqual(["active", "archived"]);
expectText(trigger, "All");
expect(trigger.getAttribute("aria-label")).toBe("Filter: All");
expect(routeNavigateMock).not.toHaveBeenCalled();
fireEvent.keyDown(archived, { key: "Escape" });
await waitFor(() => expect(screen.queryByRole("menu")).toBeNull());
expect(
screen.getByRole("combobox", { name: "Search threads" }),
).toBeTruthy();
});

it.each(["", "match"])(
"keeps saved messages with Active and budgets two groups for query '%s'",
async (query) => {
const active = Array.from({ length: 7 }, (_, index) =>
makeThread(`active-${index}`, { status: index === 1 ? "pending" : "idle" }),
);
const archived = Array.from({ length: 4 }, (_, index) =>
makeThread(`archived-${index}`, { archivedAt: 1 }),
);
modeState.activeRecents = active;
modeState.archivedRecents = archived;
modeState.searchResponse = {
active: { total: 7, results: active.map((thread) => ({ thread, matches: [] })) },
archived: { total: 4, results: archived.map((thread) => ({ thread, matches: [] })) },
};
const { store } = renderPalette({ lifecycles: ["active", "archived"] });
openThreadSearch();
const input = await screen.findByRole("combobox", { name: "Search threads" });
fireEvent.change(input, { target: { value: query } });
expect(screen.queryByRole("group", { name: "Drafts" })).toBeNull();
for (const name of ["Active", "Archived"]) {
expect(within(screen.getByRole("group", { name })).getAllByRole("option")).toHaveLength(4);
}
fireEvent.click(screen.getByRole("option", { name: "Show more threads" }));
expect(within(screen.getByRole("group", { name: "Active" })).getAllByRole("option")).toHaveLength(7);
expect(within(screen.getByRole("group", { name: "Archived" })).getAllByRole("option")).toHaveLength(4);
expectText(selectedOption(), "Title active-3");
act(() => store.set(paletteThreadLifecyclesAtom, ["active"]));
expect(screen.getByRole("option", { name: "Show more threads" })).toBeTruthy();
expect(document.querySelector("[data-palette-footer]")).toBeNull();
},
);

it("uses the shared empty treatment for selected populations and search with no matches", async () => {
renderPalette({ lifecycles: ["active", "archived"] });
openThreadSearch();
const input = await screen.findByRole("combobox", {
name: "Search threads",
});
expect(screen.getByText("No threads")).toBeTruthy();
fireEvent.change(input, { target: { value: "unmatched" } });
expect(screen.getByText("No matching threads")).toBeTruthy();
expect(screen.queryAllByRole("option")).toHaveLength(0);
expect(screen.queryByRole("button", { name: /create/i })).toBeNull();
});

it("groups lifecycle with headings while preserving highlights and attention status", async () => {
const active = makeThread("active", {
title: "Matching active thread",
Expand All @@ -1058,7 +1187,7 @@ describe("CommandPalette", () => {
},
archived: { total: 1, results: [{ thread: archived, matches: [] }] },
};
renderPalette();
renderPalette({ lifecycles: ["active", "archived"] });
openThreadSearch();
const input = await screen.findByRole("combobox", {
name: "Search threads",
Expand All @@ -1075,11 +1204,11 @@ describe("CommandPalette", () => {
within(rows[1]).getByRole("img", { name: "Unread thread succeeded" }),
).toBeTruthy();
expect(results.querySelector('[data-icon="Archive"]')).toBeNull();
expectClasses(within(results).getByText("Threads"), "px-2", "py-1");
expectClasses(within(results).getByText("Active"), "px-2", "py-1");
expectClasses(within(results).getByText("Archived"), "px-2", "py-1");
const groups = within(results).getAllByRole("group");
expect(groups).toHaveLength(2);
for (const [index, name] of ["Threads", "Archived"].entries()) {
for (const [index, name] of ["Active", "Archived"].entries()) {
const group = within(results).getByRole("group", { name });
expect(within(group).getAllByRole("option")).toEqual([rows[index]]);
const label = within(group).getByText(name);
Expand Down Expand Up @@ -1119,15 +1248,15 @@ describe("CommandPalette", () => {
results: archived.map((thread) => ({ thread, matches: [] })),
},
};
renderPalette();
renderPalette({ lifecycles: ["active", "archived"] });
openThreadSearch();
const input = await screen.findByRole("combobox", {
name: "Search threads",
});
expect(screen.getAllByRole("option")).toHaveLength(8);
expect(screen.queryByText("Show more")).toBeNull();
expect(screen.getAllByRole("option")).toHaveLength(7);
expect(screen.getByText("Show more")).toBeTruthy();
fireEvent.change(input, { target: { value: "match" } });
const activeGroup = screen.getByRole("group", { name: "Threads" });
const activeGroup = screen.getByRole("group", { name: "Active" });
const archivedGroup = screen.getByRole("group", { name: "Archived" });
expect(
within(activeGroup)
Expand All @@ -1147,7 +1276,7 @@ describe("CommandPalette", () => {
expectClasses(more, "text-xs", "text-subtle-foreground");
expectNoClasses(more, "font-medium");
expectClasses(
within(activeGroup).getByText("Threads", { selector: "div" }),
within(activeGroup).getByText("Active", { selector: "div" }),
"text-xs",
"font-normal",
"text-subtle-foreground",
Expand Down Expand Up @@ -1184,9 +1313,9 @@ describe("CommandPalette", () => {
within(archivedGroup).getAllByRole("option")[3].id,
);
fireEvent.change(input, { target: { value: "" } });
expect(screen.getByRole("group", { name: "Recent" })).toBeTruthy();
expect(screen.getAllByRole("option")).toHaveLength(8);
expect(screen.queryByText("Show more")).toBeNull();
expect(screen.getByRole("group", { name: "Active" })).toBeTruthy();
expect(screen.getAllByRole("option")).toHaveLength(7);
expect(screen.getByText("Show more")).toBeTruthy();
});

it.each(["active", "archived"] as const)(
Expand All @@ -1205,7 +1334,7 @@ describe("CommandPalette", () => {
results: threads.map((thread) => ({ thread, matches: [] })),
},
};
renderPalette();
renderPalette({ lifecycles: [lifecycle] });
openThreadSearch();
const input = await screen.findByRole("combobox", {
name: "Search threads",
Expand Down Expand Up @@ -1246,7 +1375,7 @@ describe("CommandPalette", () => {
],
},
};
renderPalette();
renderPalette({ lifecycles: ["active", "archived"] });
openThreadSearch();
const input = await screen.findByRole("combobox", {
name: "Search threads",
Expand Down Expand Up @@ -1274,10 +1403,10 @@ describe("CommandPalette", () => {
expect(
within(results).getByRole("option").getAttribute("aria-selected"),
).toBe("true");
expect(within(results).getByText("Recent")).toBeTruthy();
expect(within(results).getByText("Active")).toBeTruthy();
expect(within(results).getAllByRole("group")).toHaveLength(1);
expect(
within(within(results).getByRole("group", { name: "Recent" })).getByRole(
within(within(results).getByRole("group", { name: "Active" })).getByRole(
"option",
),
).toBe(within(results).getByRole("option"));
Expand Down Expand Up @@ -1375,10 +1504,7 @@ describe("CommandPalette", () => {
row.querySelector("[data-palette-thread-metadata]")?.textContent,
).toContain("Palette project");
}
expect(within(results).queryByText("Recent") !== null).toBe(query === "");
expect(
within(results).queryByText("Threads", { exact: true }) !== null,
).toBe(query !== "");
expect(within(results).getByText("Active")).toBeTruthy();
expect(within(results).queryByText("Archived")).toBeNull();
},
);
Expand Down Expand Up @@ -1444,7 +1570,7 @@ describe("CommandPalette", () => {
],
},
};
renderPalette();
renderPalette({ lifecycles: ["archived"] });
openThreadSearch();
const input = await screen.findByRole("combobox", {
name: "Search threads",
Expand Down
7 changes: 6 additions & 1 deletion apps/app/src/components/commands/PaletteShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface PaletteModeChipProps {
icon: Parameters<typeof Icon>[0]["name"];
label: string;
onClear: () => void;
hideShortcut?: boolean;
}

interface PaletteShellProps {
Expand All @@ -30,6 +31,7 @@ interface PaletteShellProps {
listLabel: string;
listRef?: Ref<HTMLDivElement>;
modeChip?: PaletteModeChipProps;
inputAccessory?: ReactNode;
onInputChange: (value: string) => void;
onInputKeyDown: KeyboardEventHandler<HTMLInputElement>;
placeholder: string;
Expand All @@ -46,6 +48,7 @@ export function PaletteShell({
listLabel,
listRef,
modeChip,
inputAccessory,
onInputChange,
onInputKeyDown,
placeholder,
Expand Down Expand Up @@ -93,6 +96,7 @@ export function PaletteShell({
<span id={inputDescriptionId} className="sr-only">
{inputDescription}
</span>
{inputAccessory}
</div>
</div>
<div
Expand Down Expand Up @@ -135,6 +139,7 @@ function PaletteModeChip({
icon,
label,
onClear,
hideShortcut,
}: PaletteModeChipProps) {
return (
<span
Expand All @@ -156,7 +161,7 @@ function PaletteModeChip({
closeAction={{
onClose: onClear,
closeLabel: clearLabel,
tooltip: `${clearLabel} (Esc)`,
tooltip: hideShortcut ? clearLabel : `${clearLabel} (Esc)`,
}}
/>
</span>
Expand Down
Loading
Loading