feat(coding-agent): add named-only filter toggle to /resume picker (#1128)

Adds Ctrl+N toggle to filter sessions by named-only vs all in the /resume picker.

- Add NameFilter type and filtering logic to session-selector-search.ts
- Add toggleSessionNamedFilter app keybinding (default: ctrl+n)
- Show Name: All/Named state in header
- Empty state mentions toggle keybinding as escape hatch
- Export hasSessionName() to avoid duplication

Co-authored-by: warren <warren.winter@gmail.com>
This commit is contained in:
Mario Zechner 2026-02-01 18:30:55 +01:00
parent 507639c760
commit 73839f876e
9 changed files with 203 additions and 23 deletions

View file

@ -1,4 +1,6 @@
import { beforeAll, describe, expect, it } from "vitest";
import { DEFAULT_EDITOR_KEYBINDINGS, EditorKeybindingsManager, setEditorKeybindings } from "@mariozechner/pi-tui";
import { beforeAll, beforeEach, describe, expect, it } from "vitest";
import { KeybindingsManager } from "../src/core/keybindings.js";
import type { SessionInfo } from "../src/core/session-manager.js";
import { SessionSelectorComponent } from "../src/modes/interactive/components/session-selector.js";
import { initTheme } from "../src/modes/interactive/theme/theme.js";
@ -43,6 +45,13 @@ const CTRL_D = "\x04";
const CTRL_BACKSPACE = "\x1b[127;5u";
describe("session selector path/delete interactions", () => {
const keybindings = KeybindingsManager.inMemory();
beforeEach(() => {
// Ensure test isolation: editor keybindings are a global singleton
setEditorKeybindings(new EditorKeybindingsManager(DEFAULT_EDITOR_KEYBINDINGS));
});
beforeAll(() => {
// session selector uses the global theme instance
initTheme("dark");
@ -57,6 +66,7 @@ describe("session selector path/delete interactions", () => {
() => {},
() => {},
() => {},
{ keybindings },
);
await flushPromises();
@ -80,6 +90,7 @@ describe("session selector path/delete interactions", () => {
() => {},
() => {},
() => {},
{ keybindings },
);
await flushPromises();
@ -103,6 +114,7 @@ describe("session selector path/delete interactions", () => {
() => {},
() => {},
() => {},
{ keybindings },
);
await flushPromises();
@ -138,6 +150,7 @@ describe("session selector path/delete interactions", () => {
() => {},
() => {},
() => {},
{ keybindings },
);
await flushPromises();
@ -169,6 +182,7 @@ describe("session selector path/delete interactions", () => {
() => {},
() => {},
() => {},
{ keybindings },
);
await flushPromises();

View file

@ -124,4 +124,72 @@ describe("session selector search", () => {
const result = filterAndSortSessions(sessions, "re:(", "recent");
expect(result).toEqual([]);
});
describe("name filter", () => {
const sessions: SessionInfo[] = [
makeSession({
id: "named1",
name: "My Project",
modified: new Date("2026-01-03T00:00:00.000Z"),
allMessagesText: "blueberry",
}),
makeSession({
id: "named2",
name: "Another Named",
modified: new Date("2026-01-02T00:00:00.000Z"),
allMessagesText: "blueberry",
}),
makeSession({
id: "other1",
modified: new Date("2026-01-04T00:00:00.000Z"),
allMessagesText: "blueberry",
}),
makeSession({
id: "other2",
modified: new Date("2026-01-01T00:00:00.000Z"),
allMessagesText: "blueberry",
}),
];
it("returns all sessions when nameFilter is 'all'", () => {
const result = filterAndSortSessions(sessions, "", "recent", "all");
expect(result.map((session) => session.id)).toEqual(["named1", "named2", "other1", "other2"]);
});
it("returns only named sessions when nameFilter is 'named'", () => {
const result = filterAndSortSessions(sessions, "", "recent", "named");
expect(result.map((session) => session.id)).toEqual(["named1", "named2"]);
});
it("applies name filter before search query", () => {
const result = filterAndSortSessions(sessions, "blueberry", "recent", "named");
expect(result.map((session) => session.id)).toEqual(["named1", "named2"]);
});
it("excludes whitespace-only names from named filter", () => {
const sessionsWithWhitespace: SessionInfo[] = [
makeSession({
id: "whitespace",
name: " ",
modified: new Date("2026-01-01T00:00:00.000Z"),
allMessagesText: "test",
}),
makeSession({
id: "empty",
name: "",
modified: new Date("2026-01-02T00:00:00.000Z"),
allMessagesText: "test",
}),
makeSession({
id: "named",
name: "Real Name",
modified: new Date("2026-01-03T00:00:00.000Z"),
allMessagesText: "test",
}),
];
const result = filterAndSortSessions(sessionsWithWhitespace, "", "recent", "named");
expect(result.map((session) => session.id)).toEqual(["named"]);
});
});
});