chat: pwd selector — switch the /chat working directory

The Sessions panel header gets a pwd selector: the default claude_sessions dir,
plus every directory that already has Claude sessions (auto-discovered by reading
the real cwd back from ~/.claude/projects), plus free-text entry. Switching pwd
refetches the list for that cwd's Claude project group and runs New Chat / resume
in it. Backend: GET /chat/pwds + a ?cwd= param on the session ops; the WS handler
honors a chosen cwd for /chat (default claude_sessions). Browse-modal picker next.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-24 12:41:49 +00:00
co-authored by Claude Opus 4.8
parent 420aa08783
commit 04bde70ed3
8 changed files with 237 additions and 36 deletions
+2 -2
View File
@@ -5,8 +5,8 @@ export { useDashboardState } from './useDashboardState';
export { usePiModels, useVisiblePiModels, useUserVisibleModels, useEnabledPiModels, modelKey } from './useModels';
export type { ModelOption } from './useModels';
export { useAccessPolicy } from './useAccessPolicy';
export { useClaudeSessions } from './useClaudeSessions';
export type { ClaudeSessionSummary } from './useClaudeSessions';
export { useClaudeSessions, useChatPwds } from './useClaudeSessions';
export type { ClaudeSessionSummary, ClaudePwd } from './useClaudeSessions';
export { useRecentModels } from './useRecentModels';
export { usePlans } from './usePlans';
export { useLandingPage } from './useLandingPage';
+34 -11
View File
@@ -3,7 +3,7 @@ import { useCallback } from 'react';
import { useClient } from 'hooks/useClient';
import { useAuth } from 'hooks/useAuth';
const QUERY_KEY = ['CLAUDE_SESSIONS'];
const SESSIONS_KEY = 'CLAUDE_SESSIONS';
export type ClaudeSessionSummary = {
id: string; // Claude session uuid (= transcript filename)
@@ -22,37 +22,60 @@ export type ClaudeSessionMessage =
export type ClaudeSessionDetail = { id: string; model: string; cwd: string; messages: ClaudeSessionMessage[] };
/** The /chat route's sessions, read straight from Claude's own transcript store (source of truth). */
export function useClaudeSessions() {
export type ClaudePwd = { cwd: string; sessionCount: number; updatedAt: string; isDefault: boolean };
// Query string for the selected working directory (null/undefined = the default claude_sessions dir).
const cwdQuery = (cwd?: string | null) => (cwd ? `?cwd=${encodeURIComponent(cwd)}` : '');
/** The default /chat dir plus every directory that already has Claude sessions. */
export function useChatPwds() {
const client = useClient();
const { isAuthenticated } = useAuth();
const { data } = useQuery<{ pwds: ClaudePwd[]; default: string }>({
queryKey: ['CHAT_PWDS'],
enabled: isAuthenticated,
queryFn: () => client.get<{ pwds: ClaudePwd[]; default: string }>('/chat/pwds'),
staleTime: 30 * 1000,
});
return { pwds: data?.pwds ?? [], defaultCwd: data?.default ?? null };
}
/** Sessions for a working directory, read from Claude's own transcript store (source of truth). */
export function useClaudeSessions(cwd?: string | null) {
const client = useClient();
const queryClient = useQueryClient();
const { isAuthenticated } = useAuth();
const q = cwdQuery(cwd);
const { data, isLoading, refetch } = useQuery<{ sessions: ClaudeSessionSummary[] }>({
queryKey: QUERY_KEY,
queryKey: [SESSIONS_KEY, cwd ?? 'default'],
enabled: isAuthenticated,
queryFn: () => client.get<{ sessions: ClaudeSessionSummary[] }>('/chat/sessions'),
queryFn: () => client.get<{ sessions: ClaudeSessionSummary[] }>(`/chat/sessions${q}`),
staleTime: 30 * 1000,
});
const loadSession = (id: string) => client.get<ClaudeSessionDetail>(`/chat/sessions/${id}`);
const loadSession = (id: string) => client.get<ClaudeSessionDetail>(`/chat/sessions/${id}${q}`);
const invalidate = useCallback(() => queryClient.invalidateQueries({ queryKey: QUERY_KEY }), [queryClient]);
// Invalidate every cwd's list (also refreshes pwd counts) — cheap and avoids stale lists.
const invalidate = useCallback(() => {
queryClient.invalidateQueries({ queryKey: [SESSIONS_KEY] });
queryClient.invalidateQueries({ queryKey: ['CHAT_PWDS'] });
}, [queryClient]);
const deleteSession = useCallback(
async (id: string) => {
await client.delete(`/chat/sessions/${id}`);
await client.delete(`/chat/sessions/${id}${q}`);
invalidate();
},
[client, invalidate],
[client, q, invalidate],
);
const renameSession = useCallback(
async (id: string, title: string) => {
await client.patch(`/chat/sessions/${id}/title`, { title });
await client.patch(`/chat/sessions/${id}/title${q}`, { title });
invalidate();
},
[client, invalidate],
[client, q, invalidate],
);
return { sessions: data?.sessions ?? [], isLoading, refetch, loadSession, deleteSession, renameSession, invalidate };