chat: put the session list cwd in the url, not a panel channel
Which project group you are looking at is addressable state, so /chat?cwd=<dir> has to be a
link anyone can hand out — it survives a refresh and an agent run can point straight at its
own runs directory. Was usePanelChannel('chat:active-cwd'), which per the navigation audit is
for signals and refresh buses only. Row links and New Chat now carry the query string along.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useMemo, useRef } from 'react';
|
import { useEffect, useMemo, useRef } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router';
|
import { useParams, useNavigate, useSearchParams } from 'react-router';
|
||||||
import type { LayoutNode, SelectedSession } from 'officerdev';
|
import type { LayoutNode, SelectedSession } from 'officerdev';
|
||||||
import { WorkspaceView } from 'officerdev';
|
import { WorkspaceView } from 'officerdev';
|
||||||
import { useIsMobile } from 'hooks/useIsMobile';
|
import { useIsMobile } from 'hooks/useIsMobile';
|
||||||
@@ -38,7 +38,7 @@ type SessionListPageProps = {
|
|||||||
export const SessionListPage = ({ isNew }: SessionListPageProps) => {
|
export const SessionListPage = ({ isNew }: SessionListPageProps) => {
|
||||||
const { sessionId } = useParams<{ sessionId: string }>();
|
const { sessionId } = useParams<{ sessionId: string }>();
|
||||||
const [selected, setSelected] = usePanelChannel<SelectedSession>('chat:selected-session', null);
|
const [selected, setSelected] = usePanelChannel<SelectedSession>('chat:selected-session', null);
|
||||||
const [, setActiveCwd] = usePanelChannel<string | null>('chat:active-cwd', null);
|
const [, setSearchParams] = useSearchParams();
|
||||||
const client = useClient();
|
const client = useClient();
|
||||||
const selectedRef = useRef(selected);
|
const selectedRef = useRef(selected);
|
||||||
selectedRef.current = selected;
|
selectedRef.current = selected;
|
||||||
@@ -78,7 +78,16 @@ export const SessionListPage = ({ isNew }: SessionListPageProps) => {
|
|||||||
try {
|
try {
|
||||||
const detail = await client.get<ClaudeSessionDetail>(`/chat/sessions/${sessionId}?limit=${CHAT_TAIL}`);
|
const detail = await client.get<ClaudeSessionDetail>(`/chat/sessions/${sessionId}?limit=${CHAT_TAIL}`);
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
setActiveCwd(detail.cwd || null);
|
// replace: resolving a deep link is a canonicalisation, not a navigation the back button owes you.
|
||||||
|
setSearchParams(
|
||||||
|
(prev) => {
|
||||||
|
const next = new URLSearchParams(prev);
|
||||||
|
if (detail.cwd) next.set('cwd', detail.cwd);
|
||||||
|
else next.delete('cwd');
|
||||||
|
return next;
|
||||||
|
},
|
||||||
|
{ replace: true },
|
||||||
|
);
|
||||||
setSelected({
|
setSelected({
|
||||||
id: sessionId,
|
id: sessionId,
|
||||||
model: detail.model,
|
model: detail.model,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useLocation } from 'react-router';
|
import { useLocation, useSearchParams } from 'react-router';
|
||||||
import { Unplug } from 'lucide-react';
|
import { Unplug } from 'lucide-react';
|
||||||
import { usePanelChannel } from 'hooks/usePanelChannel';
|
import { usePanelChannel } from 'hooks/usePanelChannel';
|
||||||
import { useAuth } from 'hooks/useAuth';
|
import { useAuth } from 'hooks/useAuth';
|
||||||
@@ -98,8 +98,9 @@ function NewChat({ resumeSummary, resumeSessionId, initialMessages, total, initi
|
|||||||
: undefined,
|
: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Run the session in the pwd chosen in the Sessions panel; null → backend default (general_chat_sessions).
|
// Run the session in the pwd the URL names; absent → backend default (general_chat_sessions).
|
||||||
const [activeCwd] = usePanelChannel<string | null>('chat:active-cwd', null);
|
const [searchParams] = useSearchParams();
|
||||||
|
const activeCwd = searchParams.get('cwd');
|
||||||
const cwd = activeCwd ? { path: activeCwd } : locationState?.cwd;
|
const cwd = activeCwd ? { path: activeCwd } : locationState?.cwd;
|
||||||
|
|
||||||
const initialMessage = locationState?.initialMessage
|
const initialMessage = locationState?.initialMessage
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useRef, useState, useCallback } from 'react';
|
import { useRef, useState, useCallback } from 'react';
|
||||||
import { Link, useNavigate } from 'react-router';
|
import { Link, useNavigate, useSearchParams } from 'react-router';
|
||||||
import { Plus, MessageSquare, RefreshCw, Trash2, Pencil, Check, X } from 'lucide-react';
|
import { Plus, MessageSquare, RefreshCw, Trash2, Pencil, Check, X } from 'lucide-react';
|
||||||
import { usePanelChannel } from 'hooks/usePanelChannel';
|
import { usePanelChannel } from 'hooks/usePanelChannel';
|
||||||
import { useClaudeSessions } from 'state/useClaudeSessions';
|
import { useClaudeSessions } from 'state/useClaudeSessions';
|
||||||
@@ -10,8 +10,12 @@ import { PwdSelector } from './PwdSelector';
|
|||||||
// Clicking a session loads its transcript and continues the real Claude session via --resume.
|
// Clicking a session loads its transcript and continues the real Claude session via --resume.
|
||||||
export const SessionList = () => {
|
export const SessionList = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
// The working directory the list operates on (null = the default general_chat_sessions dir).
|
// The working directory the list operates on (absent = the default general_chat_sessions dir).
|
||||||
const [activeCwd, setActiveCwd] = usePanelChannel<string | null>('chat:active-cwd', null);
|
// In the URL, not a channel: which project group you're looking at is addressable state, so
|
||||||
|
// /chat?cwd=<dir> has to be a link anyone can hand out — an agent run points at its own group
|
||||||
|
// this way, and it survives a refresh.
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
const activeCwd = searchParams.get('cwd');
|
||||||
const { sessions, isLoading, refetch, deleteSession, renameSession } = useClaudeSessions(activeCwd);
|
const { sessions, isLoading, refetch, deleteSession, renameSession } = useClaudeSessions(activeCwd);
|
||||||
const [selected, setSelected] = usePanelChannel<SelectedSession>('chat:selected-session', null);
|
const [selected, setSelected] = usePanelChannel<SelectedSession>('chat:selected-session', null);
|
||||||
const [editingId, setEditingId] = useState<string | null>(null);
|
const [editingId, setEditingId] = useState<string | null>(null);
|
||||||
@@ -52,7 +56,15 @@ export const SessionList = () => {
|
|||||||
<PwdSelector
|
<PwdSelector
|
||||||
value={activeCwd}
|
value={activeCwd}
|
||||||
onChange={(cwd) => {
|
onChange={(cwd) => {
|
||||||
setActiveCwd(cwd);
|
setSearchParams(
|
||||||
|
(prev) => {
|
||||||
|
const next = new URLSearchParams(prev);
|
||||||
|
if (cwd) next.set('cwd', cwd);
|
||||||
|
else next.delete('cwd');
|
||||||
|
return next;
|
||||||
|
},
|
||||||
|
{ replace: true },
|
||||||
|
);
|
||||||
setSelected(null); // sessions belong to a cwd — clear the open one when switching
|
setSelected(null); // sessions belong to a cwd — clear the open one when switching
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -67,7 +79,8 @@ export const SessionList = () => {
|
|||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelected({ id: `new:${Date.now()}` });
|
setSelected({ id: `new:${Date.now()}` });
|
||||||
navigate('/chat/new', { replace: true });
|
// Carry the cwd: a new chat starts in the pwd the list is showing, and that now lives in the URL.
|
||||||
|
navigate({ pathname: '/chat/new', search: searchParams.toString() }, { replace: true });
|
||||||
}}
|
}}
|
||||||
className="flex items-center gap-1.5 rounded-md bg-duck-teal hover:bg-duck-teal/90 text-duck-yellow cursor-pointer h-7 px-3 text-xs font-medium"
|
className="flex items-center gap-1.5 rounded-md bg-duck-teal hover:bg-duck-teal/90 text-duck-yellow cursor-pointer h-7 px-3 text-xs font-medium"
|
||||||
>
|
>
|
||||||
@@ -132,7 +145,7 @@ export const SessionList = () => {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Link
|
<Link
|
||||||
to={`/chat/${session.id}`}
|
to={{ pathname: `/chat/${session.id}`, search: searchParams.toString() }}
|
||||||
className="flex flex-1 items-center gap-3 px-4 py-3 min-w-0 text-left cursor-pointer"
|
className="flex flex-1 items-center gap-3 px-4 py-3 min-w-0 text-left cursor-pointer"
|
||||||
>
|
>
|
||||||
<MessageSquare className="h-4 w-4 shrink-0 text-duck-teal/60" />
|
<MessageSquare className="h-4 w-4 shrink-0 text-duck-teal/60" />
|
||||||
|
|||||||
Reference in New Issue
Block a user