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
@@ -36,6 +36,8 @@ export function WorkspaceSessionList({
liveRuns,
query,
selectedRunId,
onMatches,
hidden = false,
}: {
lane: Lane;
/** Every live dashboard pane, from `GET /api/run`. */
@@ -44,6 +46,11 @@ export function WorkspaceSessionList({
query: string;
/** The `run` the Workspace page is attached to, to mark its row. */
selectedRunId: string | null;
/** How many sessions match `query`, once they are loaded — lets the
* sidebar search find a workspace by a session's name. */
onMatches?: (count: number) => void;
/** Mounted only to load and count, during a search that hides the row. */
hidden?: boolean;
}) {
const { t } = useTranslation("lanes");
const navigate = useNavigate();
@@ -67,7 +74,12 @@ export function WorkspaceSessionList({
(s) => !q || (s.name ?? "").toLowerCase().includes(q) || s.id.startsWith(q)
);
const runFor = (s: Session) => liveRunFor(lane, liveRuns, s.id);
const count = sessions === null ? null : shown.length;
useEffect(() => {
if (count !== null) onMatches?.(count);
}, [count, onMatches]);
if (hidden) return null;
if (sessions !== null && shown.length === 0) {
return <p className="py-1 pl-8 text-[11px] text-fg-muted">{t("tree.noSessions")}</p>;
}