name the live sessions instead of showing their ids

The titles were looked up client-side against the sessions query already in cache, which only covers the
group being browsed — so anything running in another directory rendered as a truncated uuid, which is
most of them.

Resolved server-side now. The agent reports keys and nothing else, so liveSessionTitle finds the
transcript by scanning the project slugs, takes the cwd off its own first entry, and hands that to
claudeSessionContext — the same path the list uses, so the two agree on naming, /clear chains merged
included, rather than offering a second opinion.

A null title means no transcript has been written yet. That row says 'Starting…' and is deliberately not
a link: pointing at a session you cannot open yet is worse than plainly not being a link.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 01:18:54 +01:00
co-authored by Claude Opus 5
parent 8f2d80573a
commit 27bbbaee47
4 changed files with 77 additions and 18 deletions
@@ -2,9 +2,7 @@ import { useParams } from 'react-router';
import { Activity, Loader2, Radio } from 'lucide-react';
import { DataList, DataRow, EmptyBlock, ErrorBlock, LoadingBlock, MetaItem } from '@/components/Data';
import { useLiveSessions } from 'state/useLiveSessions';
import { useClaudeSessions } from 'state/useClaudeSessions';
import { useSelectedChatSession } from '../../channels';
import { cwdFromSplat, chatSessionPath } from './chat-routes';
import { chatSessionPath } from './chat-routes';
/**
* What the agent is actually running, as opposed to what it has ever run.
@@ -16,17 +14,10 @@ import { cwdFromSplat, chatSessionPath } from './chat-routes';
* record of a live turn and only the agent can say.
*/
export const LiveSessions = () => {
const { sessionId, '*': splat } = useParams<{ sessionId: string; '*': string }>();
const [selected] = useSelectedChatSession();
// Only to highlight the row you already have open; the titles come from the server now.
const { sessionId } = useParams<{ sessionId: string }>();
const { live, isLoading, error, refetch } = useLiveSessions();
// Titles for free, when we happen to have them. This is the same query key the list below already
// holds, so it costs no request — but it only covers the group being browsed, and a live session can
// be in any of them. Hence the fallback to a short key rather than pretending we know.
const activeCwd = cwdFromSplat(splat) ?? selected?.cwd ?? null;
const { sessions } = useClaudeSessions(activeCwd);
const titleFor = (key: string) => sessions.find((session) => session.id === key)?.title;
if (isLoading && live.length === 0) return <LoadingBlock label="Asking the agent…" />;
if (error) {
return (
@@ -49,17 +40,17 @@ export const LiveSessions = () => {
return (
<DataList>
{live.map((session) => {
const title = titleFor(session.sessionKey);
// A conversation that has not been saved yet has no transcript to open, so it is shown but not
// linked — a row that navigates nowhere is worse than one that plainly isn't a link.
const isDraft = session.sessionKey.startsWith('new:');
// No title means no transcript on disk yet — a conversation whose first turn has not landed. It
// is shown but not linked: a row that navigates to a session you cannot open is worse than one
// that plainly isn't a link.
const isDraft = session.title === null;
return (
<DataRow
key={session.sessionKey}
to={isDraft ? undefined : chatSessionPath(session.sessionKey)}
selected={session.sessionKey === sessionId}
title={title ?? (isDraft ? 'Unsaved chat' : `${session.sessionKey.slice(0, 8)}`)}
title={session.title ?? 'Starting…'}
meta={[
session.isGenerating ? (
<MetaItem key="g" icon={Loader2}>
@@ -13,6 +13,14 @@ export type LiveSession = {
isGenerating: boolean;
/** Background tasks started but not yet notified — `run_in_background`, Monitor, and friends. */
pendingTasks: number;
/**
* Resolved server-side, and null when no transcript has been written yet.
*
* It has to come from there: the agent reports keys, and a client can only name the sessions in the
* group it is currently browsing — so anything running in another directory showed a raw id.
*/
title: string | null;
cwd: string | null;
};
const LIVE_KEY = 'CHAT_LIVE_SESSIONS';