feat(ui): finish the workspace-sessions plan — tool filter, session search, explicit Resume

- Sidebar: a tool filter (shown once workspaces of two tools exist, kept in
  agenthub.sidebar.projects → tool; reordering under it keeps the hidden
  workspaces' places), and search that also finds a workspace by one of its
  session names, showing only the matching sessions.
- Workspace home: the repository it belongs to (root, main branch, worktree
  count) under the title, and each recent's cost.
- Transcript page: an explicit Resume for a Claude session started in a
  workspace's own folder (SessionResumeButton). It warns that a resume re-reads
  the whole transcript, re-reads the live panes, then starts in the first free
  pane and opens it; a live session offers Open pane instead.
This commit is contained in:
2026-10-02 01:35:34 +07:00
parent d0535af626
commit 5b269698f6
15 changed files with 597 additions and 36 deletions
+6 -1
View File
@@ -1,7 +1,8 @@
/**
* @file projectTreePrefs — the sidebar workspace list's viewer preferences, in
* ONE localStorage key (`agenthub.sidebar.projects`): which workspaces are
* expanded to their sessions (by lane id), and — Settings → Worktrees —
* expanded to their sessions (by lane id), the tool the list is filtered to,
* and — Settings → Worktrees —
* whether a workspace home's Add folder suggests the repo's untracked
* worktrees and which sources of them (`claude` = `<repo>/.claude/worktrees/*`,
* `agenthub` = LANES_ROOT) it leaves out. A `useSyncExternalStore` reader that caches nothing
@@ -17,12 +18,15 @@ export const PROJECT_TREE_KEY = "agenthub.sidebar.projects";
export interface ProjectTreePrefs {
/** Lane id → its sessions are shown under it in the sidebar. */
expanded: Record<string, boolean>;
/** Show only this tool's workspaces; null = every tool. */
tool: string | null;
showUntracked: boolean;
hideSources: string[];
}
const DEFAULTS: ProjectTreePrefs = {
expanded: {},
tool: null,
showUntracked: true,
hideSources: [],
};
@@ -45,6 +49,7 @@ export function readProjectTreePrefs(): ProjectTreePrefs {
const parsed = raw ? (JSON.parse(raw) as Partial<ProjectTreePrefs>) : {};
value = {
expanded: parsed.expanded && typeof parsed.expanded === "object" ? parsed.expanded : {},
tool: typeof parsed.tool === "string" && parsed.tool ? parsed.tool : null,
showUntracked: parsed.showUntracked !== false,
hideSources: Array.isArray(parsed.hideSources) ? parsed.hideSources : [],
};