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
31 changes: 22 additions & 9 deletions packages/ui/src/components/session-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const SessionList: Component<SessionListProps> = (props) => {
const [filterQuery, setFilterQuery] = createSignal("")
const [sortBy, setSortBy] = createSignal<SessionFamilySort>("activity")
const [worktreeDirectory, setWorktreeDirectory] = createSignal("")
const [includeMainSessions, setIncludeMainSessions] = createSignal(true)
const [includeSubsessions, setIncludeSubsessions] = createSignal(false)
const normalizedQuery = createMemo(() => (props.enableFilterBar ? filterQuery().trim().toLowerCase() : ""))
let failedSortExhaustion: string | undefined
Expand Down Expand Up @@ -241,6 +242,7 @@ const SessionList: Component<SessionListProps> = (props) => {
sort: sortBy(),
worktreeDirectory: worktreeDirectory(),
includeSubsessions: includeSubsessions(),
includeMainSessions: includeMainSessions(),
getWorktreeLabel,
...(query && !hasSearchResults
? { matchesSession: (session) => sessionMatchesQuery(session.id, query) }
Expand Down Expand Up @@ -877,15 +879,26 @@ const SessionList: Component<SessionListProps> = (props) => {
</select>
</div>

<label class="mt-2 flex items-center gap-2 text-xs text-secondary">
<input
type="checkbox"
role="switch"
checked={includeSubsessions()}
onChange={(event) => setIncludeSubsessions(event.currentTarget.checked)}
/>
{t("sessionList.filter.includeSubsessions")}
</label>
<div class="mt-2 flex flex-wrap items-center gap-x-3 gap-y-2 text-xs text-secondary">
<label class="flex items-center gap-2">
<input
type="checkbox"
role="switch"
checked={includeMainSessions()}
onChange={(event) => setIncludeMainSessions(event.currentTarget.checked)}
/>
{t("sessionList.filter.includeMainSessions")}
</label>
<label class="flex items-center gap-2">
<input
type="checkbox"
role="switch"
checked={includeSubsessions()}
onChange={(event) => setIncludeSubsessions(event.currentTarget.checked)}
/>
{t("sessionList.filter.includeSubsessions")}
</label>
</div>

<Show when={selectedCount() > 0}>
<div class="mt-2 flex items-center justify-end gap-2">
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/lib/i18n/messages/de/session.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const sessionMessages = {
"sessionList.filter.includeMainSessions": "Hauptsitzungen anzeigen",
"sessionList.filter.includeSubsessions": "Untersitzungen anzeigen",
"session.pruning.maintenance_required": "Die Sitzung oder ihr Speicher ist beschäftigt. Warte, bis der aktuelle Vorgang abgeschlossen ist, und versuche die Bereinigung erneut.",
"session.pruning.unavailable": "Das Bereinigungs-Plugin ist nicht verfügbar oder hat nicht geantwortet. Öffne CodeNomad erneut und versuche es noch einmal.",
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/lib/i18n/messages/en/session.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const sessionMessages = {
"sessionList.filter.includeMainSessions": "Show main sessions",
"sessionList.filter.includeSubsessions": "Show subsessions",
"session.pruning.maintenance_required": "The session or its storage is busy. Wait for the current operation to finish, then retry cleanup.",
"session.pruning.unavailable": "The cleanup plugin is unavailable or did not respond. Reopen CodeNomad and retry.",
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/lib/i18n/messages/es/session.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const sessionMessages = {
"sessionList.filter.includeMainSessions": "Mostrar sesiones principales",
"sessionList.filter.includeSubsessions": "Mostrar subsesiones",
"session.pruning.maintenance_required": "La sesión o su almacenamiento está ocupado. Espera a que termine la operación actual y vuelve a intentar la limpieza.",
"session.pruning.unavailable": "El complemento de limpieza no está disponible o no respondió. Vuelve a abrir CodeNomad e inténtalo de nuevo.",
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/lib/i18n/messages/fr/session.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const sessionMessages = {
"sessionList.filter.includeMainSessions": "Afficher les sessions principales",
"sessionList.filter.includeSubsessions": "Afficher les sous-sessions",
"session.pruning.maintenance_required": "La session ou son stockage est occupé. Attendez la fin de l’opération en cours, puis réessayez le nettoyage.",
"session.pruning.unavailable": "Le plugin de nettoyage est indisponible ou n’a pas répondu. Rouvrez CodeNomad puis réessayez.",
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/lib/i18n/messages/he/session.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const sessionMessages = {
"sessionList.filter.includeMainSessions": "הצגת סשנים ראשיים",
"sessionList.filter.includeSubsessions": "הצגת תת־סשנים",
"session.pruning.maintenance_required": "הסשן או האחסון שלו עסוקים. יש להמתין לסיום הפעולה הנוכחית ולנסות לנקות שוב.",
"session.pruning.unavailable": "תוסף הניקוי אינו זמין או שלא הגיב. יש לפתוח את CodeNomad מחדש ולנסות שוב.",
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/lib/i18n/messages/ja/session.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const sessionMessages = {
"sessionList.filter.includeMainSessions": "メインセッションを表示",
"sessionList.filter.includeSubsessions": "サブセッションを表示",
"session.pruning.maintenance_required": "セッションまたはストレージが使用中です。現在の処理が完了してから、クリーンアップを再試行してください。",
"session.pruning.unavailable": "クリーンアッププラグインが利用できないか、応答しませんでした。CodeNomadを開き直して再試行してください。",
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/lib/i18n/messages/ne/session.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const sessionMessages = {
"sessionList.filter.includeMainSessions": "मुख्य सत्रहरू देखाउनुहोस्",
"sessionList.filter.includeSubsessions": "उप-सत्रहरू देखाउनुहोस्",
"session.pruning.maintenance_required": "सत्र वा यसको भण्डारण व्यस्त छ। हालको कार्य सकिएपछि सफाइ फेरि प्रयास गर्नुहोस्।",
"session.pruning.unavailable": "सफाइ प्लगइन उपलब्ध छैन वा प्रतिक्रिया दिएन। CodeNomad फेरि खोलेर प्रयास गर्नुहोस्।",
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/lib/i18n/messages/ru/session.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const sessionMessages = {
"sessionList.filter.includeMainSessions": "Показывать основные сессии",
"sessionList.filter.includeSubsessions": "Показывать подсессии",
"session.pruning.maintenance_required": "Сессия или её хранилище заняты. Дождитесь завершения текущей операции и повторите очистку.",
"session.pruning.unavailable": "Плагин очистки недоступен или не ответил. Откройте CodeNomad заново и повторите попытку.",
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/lib/i18n/messages/tr/session.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const sessionMessages = {
"sessionList.filter.includeMainSessions": "Ana oturumları göster",
"sessionList.filter.includeSubsessions": "Alt oturumları göster",
"session.pruning.maintenance_required": "Oturum veya depolama alanı meşgul. Geçerli işlem bitince temizlemeyi yeniden deneyin.",
"session.pruning.unavailable": "Temizleme eklentisi kullanılamıyor veya yanıt vermedi. CodeNomad'i yeniden açıp tekrar deneyin.",
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/lib/i18n/messages/zh-Hans/session.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const sessionMessages = {
"sessionList.filter.includeMainSessions": "显示主会话",
"sessionList.filter.includeSubsessions": "显示子会话",
"session.pruning.maintenance_required": "会话或其存储正忙。请等待当前操作完成后重试清理。",
"session.pruning.unavailable": "清理插件不可用或未响应。请重新打开 CodeNomad 后重试。",
Expand Down
6 changes: 3 additions & 3 deletions packages/ui/src/stores/session-tree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ describe("session tree", () => {
const grandchild = sessions.get("grandchild")!
const project = (worktreeDirectory: string, includeSubsessions = true) => projectSessionSearchResults(
sessions.values(),
{ sort: "activity", worktreeDirectory, includeSubsessions, getWorktreeLabel: directory => directory },
{ sort: "activity", worktreeDirectory, includeMainSessions: true, includeSubsessions, getWorktreeLabel: directory => directory },
)
assert.deepEqual(project("D:\\repo").map(thread => thread.session.id), ["local-root", "grandchild", "child"])
assert.deepEqual(collectSessionThreadIds(project("D:\\repo", false)), ["local-root"])
Expand All @@ -238,14 +238,14 @@ describe("session tree", () => {
grandchild.status = "idle"
assert.equal(project("D:\\repo").length, 3)
const match = projectSessionSearchResults([grandchild], {
sort: "name", worktreeDirectory: "D:\\repo", includeSubsessions: true,
sort: "name", worktreeDirectory: "D:\\repo", includeMainSessions: true, includeSubsessions: true,
matchesSession: item => item.id === "grandchild", getWorktreeLabel: directory => directory,
})
assert.deepEqual(collectSessionThreadIds(match), ["grandchild"], "a result renders even without its parents loaded")
assert.equal(match[0].depth, 0)
assert.equal(match[0].hasChildren, false)
assert.equal(projectSessionSearchResults([grandchild], {
sort: "worktree", worktreeDirectory: parent.location.directory, includeSubsessions: true,
sort: "worktree", worktreeDirectory: parent.location.directory, includeMainSessions: true, includeSubsessions: true,
matchesSession: item => item.id === "grandchild", getWorktreeLabel: directory => directory,
}).length, 0, "text and directory must match the same session")
})
Expand Down
3 changes: 2 additions & 1 deletion packages/ui/src/stores/session-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,12 @@ export function projectSessionFamilies(
// the session itself, without adding ancestors or inheriting descendant activity.
export function projectSessionSearchResults(
sessions: Iterable<Session>,
options: SessionFamilyProjection & { includeSubsessions: boolean },
options: SessionFamilyProjection & { includeMainSessions: boolean; includeSubsessions: boolean },
): SessionThread[] {
const rows = new Map<string, SessionThread>()
for (const session of sessions) {
if (session.parentId && !options.includeSubsessions) continue
if (!session.parentId && !options.includeMainSessions) continue
rows.set(session.id, { session, children: [], depth: 0, hasChildren: false, latestUpdated: session.time.updated })
}
return projectSessionFamilies([...rows.values()], options)
Expand Down
9 changes: 9 additions & 0 deletions packages/ui/tests/browser/session-search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,17 @@ test("search is flat, filters each session, selects only matches and restores th
await page.goto(url)
await page.waitForFunction(() => Boolean((window as any).fixture))
const toggle = page.getByRole("switch", { name: "Show subsessions" })
const mainToggle = page.getByRole("switch", { name: "Show main sessions" })
await page.locator('.session-item-base[data-session-id="parent"]').waitFor()
assert.deepEqual(await rows(), ["local", "parent"])
assert.equal(await mainToggle.isChecked(), true)
assert.equal(await page.getByRole("switch").first().evaluate(el => el.closest("label")?.textContent?.trim()), "Show main sessions")
await mainToggle.uncheck()
assert.deepEqual(await rows(), [])
await toggle.check()
await page.locator('[data-session-id="child"]').waitFor()
assert.deepEqual(await rows(), ["child", "grandchild"])
await mainToggle.check()
assert.deepEqual(await rows(), ["child", "grandchild", "local", "parent"])
assert.equal(await page.locator(".session-item-nested").count(), 0)
assert.equal(await page.locator(".session-item-expander[aria-expanded]").count(), 0)
Expand Down Expand Up @@ -68,6 +76,7 @@ test("search is flat, filters each session, selects only matches and restores th
await page.locator('[data-session-id="orphan"]').waitFor()
assert.deepEqual(await rows(), ["orphan"], "server result without its parent is independently renderable")
assert.equal(await page.evaluate(() => (window as any).fixture.parentReads()), 0)
await mainToggle.uncheck()
await page.evaluate(() => (window as any).fixture.setSearchMode(false))
await page.locator('[data-session-id="parent"]').waitFor()
assert.equal(await page.getByRole("switch").count(), 0)
Expand Down
Loading