resolve /chat/<id> by scanning project groups and make session rows real links

deep-linking or refreshing /chat/<id> only has the id, so add loadClaudeSessionById
to scan every project group for the transcript and return its real cwd. the session
list page resolves on load from that, setting the cwd picker and resuming. session
rows are now <Link to=/chat/<id>> so clicking updates the url and flows through the
same resolve path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 03:35:47 +00:00
co-authored by Claude Opus 4.8
parent 32749eb665
commit 3682269936
4 changed files with 70 additions and 32 deletions
@@ -1,10 +1,9 @@
import { useRef, useState, useCallback } from 'react';
import { useNavigate } from 'react-router';
import { Plus, MessageSquare, RefreshCw, Loader2, Trash2, Pencil, Check, X } from 'lucide-react';
import { Link, useNavigate } from 'react-router';
import { Plus, MessageSquare, RefreshCw, Trash2, Pencil, Check, X } from 'lucide-react';
import { usePanelChannel } from 'hooks/usePanelChannel';
import { useClaudeSessions } from 'state/useClaudeSessions';
import type { SelectedSession } from './ChatDetailPanel';
import type { ChatMessage } from '../Chat/types';
import { PwdSelector } from './PwdSelector';
// Reads the /chat conversation list from Claude's own transcript store (source of truth).
@@ -13,9 +12,8 @@ export const SessionList = () => {
const navigate = useNavigate();
// The working directory the list operates on (null = the default general_chat_sessions dir).
const [activeCwd, setActiveCwd] = usePanelChannel<string | null>('chat:active-cwd', null);
const { sessions, isLoading, refetch, loadSession, deleteSession, renameSession } = useClaudeSessions(activeCwd);
const { sessions, isLoading, refetch, deleteSession, renameSession } = useClaudeSessions(activeCwd);
const [selected, setSelected] = usePanelChannel<SelectedSession>('chat:selected-session', null);
const [openingId, setOpeningId] = useState<string | null>(null);
const [editingId, setEditingId] = useState<string | null>(null);
const [editValue, setEditValue] = useState('');
const [confirmingId, setConfirmingId] = useState<string | null>(null);
@@ -28,17 +26,6 @@ export const SessionList = () => {
}
}, []);
const handleSelect = async (id: string) => {
if (openingId || editingId) return;
setOpeningId(id);
try {
const detail = await loadSession(id);
setSelected({ id, model: detail.model, resumeSessionId: id, initialMessages: detail.messages as ChatMessage[] });
} finally {
setOpeningId(null);
}
};
const startRename = (id: string, current: string) => {
setConfirmingId(null);
setEditingId(id);
@@ -144,16 +131,11 @@ export const SessionList = () => {
</div>
) : (
<>
<button
onClick={() => handleSelect(session.id)}
disabled={!!openingId}
<Link
to={`/chat/${session.id}`}
className="flex flex-1 items-center gap-3 px-4 py-3 min-w-0 text-left cursor-pointer"
>
{openingId === session.id ? (
<Loader2 className="h-4 w-4 shrink-0 text-duck-teal/60 animate-spin" />
) : (
<MessageSquare className="h-4 w-4 shrink-0 text-duck-teal/60" />
)}
<MessageSquare className="h-4 w-4 shrink-0 text-duck-teal/60" />
<div className="min-w-0 flex-1">
<div className="text-sm font-medium text-duck-dark/80 dark:text-foreground/80 truncate">
{session.title}
@@ -179,7 +161,7 @@ export const SessionList = () => {
)}
</div>
</div>
</button>
</Link>
{isConfirming ? (
<div className="flex shrink-0 items-center gap-1 mr-2">