diff --git a/packages/ui/src/components/session-list.tsx b/packages/ui/src/components/session-list.tsx index 22ec22802..78d34ead4 100644 --- a/packages/ui/src/components/session-list.tsx +++ b/packages/ui/src/components/session-list.tsx @@ -72,6 +72,7 @@ const SessionList: Component = (props) => { const [filterQuery, setFilterQuery] = createSignal("") const [sortBy, setSortBy] = createSignal("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 @@ -241,6 +242,7 @@ const SessionList: Component = (props) => { sort: sortBy(), worktreeDirectory: worktreeDirectory(), includeSubsessions: includeSubsessions(), + includeMainSessions: includeMainSessions(), getWorktreeLabel, ...(query && !hasSearchResults ? { matchesSession: (session) => sessionMatchesQuery(session.id, query) } @@ -877,15 +879,26 @@ const SessionList: Component = (props) => { - +
+ + +
0}>
diff --git a/packages/ui/src/lib/i18n/messages/de/session.ts b/packages/ui/src/lib/i18n/messages/de/session.ts index f9257e0e1..6ef590446 100644 --- a/packages/ui/src/lib/i18n/messages/de/session.ts +++ b/packages/ui/src/lib/i18n/messages/de/session.ts @@ -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.", diff --git a/packages/ui/src/lib/i18n/messages/en/session.ts b/packages/ui/src/lib/i18n/messages/en/session.ts index 6120299e6..8205edb70 100644 --- a/packages/ui/src/lib/i18n/messages/en/session.ts +++ b/packages/ui/src/lib/i18n/messages/en/session.ts @@ -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.", diff --git a/packages/ui/src/lib/i18n/messages/es/session.ts b/packages/ui/src/lib/i18n/messages/es/session.ts index 3efc7bb81..2d48fd312 100644 --- a/packages/ui/src/lib/i18n/messages/es/session.ts +++ b/packages/ui/src/lib/i18n/messages/es/session.ts @@ -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.", diff --git a/packages/ui/src/lib/i18n/messages/fr/session.ts b/packages/ui/src/lib/i18n/messages/fr/session.ts index e07e0a69e..7035b1d62 100644 --- a/packages/ui/src/lib/i18n/messages/fr/session.ts +++ b/packages/ui/src/lib/i18n/messages/fr/session.ts @@ -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.", diff --git a/packages/ui/src/lib/i18n/messages/he/session.ts b/packages/ui/src/lib/i18n/messages/he/session.ts index 800b9e0b8..66e4a72b2 100644 --- a/packages/ui/src/lib/i18n/messages/he/session.ts +++ b/packages/ui/src/lib/i18n/messages/he/session.ts @@ -1,4 +1,5 @@ export const sessionMessages = { + "sessionList.filter.includeMainSessions": "הצגת סשנים ראשיים", "sessionList.filter.includeSubsessions": "הצגת תת־סשנים", "session.pruning.maintenance_required": "הסשן או האחסון שלו עסוקים. יש להמתין לסיום הפעולה הנוכחית ולנסות לנקות שוב.", "session.pruning.unavailable": "תוסף הניקוי אינו זמין או שלא הגיב. יש לפתוח את CodeNomad מחדש ולנסות שוב.", diff --git a/packages/ui/src/lib/i18n/messages/ja/session.ts b/packages/ui/src/lib/i18n/messages/ja/session.ts index 4ef411048..16ba91e68 100644 --- a/packages/ui/src/lib/i18n/messages/ja/session.ts +++ b/packages/ui/src/lib/i18n/messages/ja/session.ts @@ -1,4 +1,5 @@ export const sessionMessages = { + "sessionList.filter.includeMainSessions": "メインセッションを表示", "sessionList.filter.includeSubsessions": "サブセッションを表示", "session.pruning.maintenance_required": "セッションまたはストレージが使用中です。現在の処理が完了してから、クリーンアップを再試行してください。", "session.pruning.unavailable": "クリーンアッププラグインが利用できないか、応答しませんでした。CodeNomadを開き直して再試行してください。", diff --git a/packages/ui/src/lib/i18n/messages/ne/session.ts b/packages/ui/src/lib/i18n/messages/ne/session.ts index 124ee97bc..e06fceca9 100644 --- a/packages/ui/src/lib/i18n/messages/ne/session.ts +++ b/packages/ui/src/lib/i18n/messages/ne/session.ts @@ -1,4 +1,5 @@ export const sessionMessages = { + "sessionList.filter.includeMainSessions": "मुख्य सत्रहरू देखाउनुहोस्", "sessionList.filter.includeSubsessions": "उप-सत्रहरू देखाउनुहोस्", "session.pruning.maintenance_required": "सत्र वा यसको भण्डारण व्यस्त छ। हालको कार्य सकिएपछि सफाइ फेरि प्रयास गर्नुहोस्।", "session.pruning.unavailable": "सफाइ प्लगइन उपलब्ध छैन वा प्रतिक्रिया दिएन। CodeNomad फेरि खोलेर प्रयास गर्नुहोस्।", diff --git a/packages/ui/src/lib/i18n/messages/ru/session.ts b/packages/ui/src/lib/i18n/messages/ru/session.ts index f7258f708..951534c43 100644 --- a/packages/ui/src/lib/i18n/messages/ru/session.ts +++ b/packages/ui/src/lib/i18n/messages/ru/session.ts @@ -1,4 +1,5 @@ export const sessionMessages = { + "sessionList.filter.includeMainSessions": "Показывать основные сессии", "sessionList.filter.includeSubsessions": "Показывать подсессии", "session.pruning.maintenance_required": "Сессия или её хранилище заняты. Дождитесь завершения текущей операции и повторите очистку.", "session.pruning.unavailable": "Плагин очистки недоступен или не ответил. Откройте CodeNomad заново и повторите попытку.", diff --git a/packages/ui/src/lib/i18n/messages/tr/session.ts b/packages/ui/src/lib/i18n/messages/tr/session.ts index f33b9d3f5..efa3b0599 100644 --- a/packages/ui/src/lib/i18n/messages/tr/session.ts +++ b/packages/ui/src/lib/i18n/messages/tr/session.ts @@ -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.", diff --git a/packages/ui/src/lib/i18n/messages/zh-Hans/session.ts b/packages/ui/src/lib/i18n/messages/zh-Hans/session.ts index ee7422ba9..306edda3e 100644 --- a/packages/ui/src/lib/i18n/messages/zh-Hans/session.ts +++ b/packages/ui/src/lib/i18n/messages/zh-Hans/session.ts @@ -1,4 +1,5 @@ export const sessionMessages = { + "sessionList.filter.includeMainSessions": "显示主会话", "sessionList.filter.includeSubsessions": "显示子会话", "session.pruning.maintenance_required": "会话或其存储正忙。请等待当前操作完成后重试清理。", "session.pruning.unavailable": "清理插件不可用或未响应。请重新打开 CodeNomad 后重试。", diff --git a/packages/ui/src/stores/session-tree.test.ts b/packages/ui/src/stores/session-tree.test.ts index ca39e208d..ca20cad9e 100644 --- a/packages/ui/src/stores/session-tree.test.ts +++ b/packages/ui/src/stores/session-tree.test.ts @@ -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"]) @@ -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") }) diff --git a/packages/ui/src/stores/session-tree.ts b/packages/ui/src/stores/session-tree.ts index 2550543c1..f31d2a72a 100644 --- a/packages/ui/src/stores/session-tree.ts +++ b/packages/ui/src/stores/session-tree.ts @@ -59,11 +59,12 @@ export function projectSessionFamilies( // the session itself, without adding ancestors or inheriting descendant activity. export function projectSessionSearchResults( sessions: Iterable, - options: SessionFamilyProjection & { includeSubsessions: boolean }, + options: SessionFamilyProjection & { includeMainSessions: boolean; includeSubsessions: boolean }, ): SessionThread[] { const rows = new Map() 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) diff --git a/packages/ui/tests/browser/session-search.test.ts b/packages/ui/tests/browser/session-search.test.ts index 108c57d1a..4001c8ba7 100644 --- a/packages/ui/tests/browser/session-search.test.ts +++ b/packages/ui/tests/browser/session-search.test.ts @@ -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) @@ -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)