From 433fffc297f1b7a6591633a6035a6b9c2480e1ca Mon Sep 17 00:00:00 2001 From: phathur Date: Thu, 16 Jul 2026 16:12:40 +0200 Subject: [PATCH] feat(desktop): improve session list sidebar - Add "No project" bucket for unowned sessions (empty cwd, home directory, ~/.hermes path) that were previously hidden - Increase project preview session limit from 3 to 20, so users can see more recent sessions without drilling into projects - Fix frontend mapping logic to support pathless synthetic groups like No project Closes issue where users couldn't see sessions without explicit project assignment, and only saw 3 most recent sessions per project in overview. --- .../chat/sidebar/projects/workspace-groups.ts | 7 +-- apps/desktop/src/store/projects.ts | 2 +- tui_gateway/project_tree.py | 49 +++++++++++++++++++ 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/app/chat/sidebar/projects/workspace-groups.ts b/apps/desktop/src/app/chat/sidebar/projects/workspace-groups.ts index 04e18f7c7b95d..96bb08d499312 100644 --- a/apps/desktop/src/app/chat/sidebar/projects/workspace-groups.ts +++ b/apps/desktop/src/app/chat/sidebar/projects/workspace-groups.ts @@ -582,10 +582,7 @@ export function overlayLivePreviews( const out: Record = {} for (const node of projects) { - if (!node.path) { - continue - } - + const key = node.path ?? node.id const liveRows = byProject.get(node.id) ?? [] const base = (node.previewSessions ?? []).filter(session => !removed.has(session.id)) @@ -602,7 +599,7 @@ export function overlayLivePreviews( } } - out[node.path] = [...map.values()].sort((a, b) => sessionRecency(b) - sessionRecency(a)).slice(0, limit) + out[key] = [...map.values()].sort((a, b) => sessionRecency(b) - sessionRecency(a)).slice(0, limit) } return out diff --git a/apps/desktop/src/store/projects.ts b/apps/desktop/src/store/projects.ts index 7504f6686031a..755db10c248bc 100644 --- a/apps/desktop/src/store/projects.ts +++ b/apps/desktop/src/store/projects.ts @@ -257,7 +257,7 @@ export async function refreshProjectTree(): Promise { $projectTreeLoading.set(true) try { - const res = await gatewayRequest('projects.tree', { preview_limit: 3 }) + const res = await gatewayRequest('projects.tree', { preview_limit: 20 }) // The flat Sessions list shows everything; scoped ids are only used here to // reconcile the optimistic eviction layer against what the server still lists. const scoped = new Set(res.scoped_session_ids ?? []) diff --git a/tui_gateway/project_tree.py b/tui_gateway/project_tree.py index 3f243153964c9..d3bc89701c984 100644 --- a/tui_gateway/project_tree.py +++ b/tui_gateway/project_tree.py @@ -520,6 +520,55 @@ def _last_active(project_sessions: list[dict]) -> float: result: list[dict] = [] + # Add "No project" bucket for unowned sessions that can't be auto-grouped + no_project_sessions: list[dict] = [] + for session in unowned: + root = _session_repo_root(session, resolve) + if root and _junk(root): + # Junk git repo (home, hermes_home), don't create auto project + no_project_sessions.append(session) + continue + if root: + # Valid repo, will be handled by auto-project tier + continue + cwd = (session.get("cwd") or "").strip() + if not cwd or _junk_cwd(cwd): + # Empty cwd or junk cwd (home/hermes_home root), no project + no_project_sessions.append(session) + continue + placement = _place(cwd, (session.get("git_branch") or "").strip(), resolve, (session.get("git_repo_root") or "").strip()) + if not placement: + # No valid placement, add to no project + no_project_sessions.append(session) + + if no_project_sessions: + scoped_ids.extend(s["id"] for s in no_project_sessions if s.get("id")) + result.append( + _project_node( + pid="__no_project__", + label="No project", + path=None, + repos=[{ + "id": "__no_project__", + "label": "No project", + "path": None, + "groups": [{ + "id": "__no_project__", + "label": "", + "path": None, + "sessions": [] if not hydrate else sorted(no_project_sessions, key=_session_time, reverse=True), + "sessionCount": len(no_project_sessions), + "isHome": False, + "isKanban": False, + }], + "sessionCount": len(no_project_sessions), + }], + session_count=len(no_project_sessions), + last_active=_last_active(no_project_sessions), + preview_sessions=_previews(no_project_sessions), + ) + ) + # Tier 1: explicit, user-created projects (always shown, even with 0 sessions). for project in active_projects: psessions = by_project.get(project["id"], [])