From 6b46d9f1f93a848bcb0189e375d3240caa3e6aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Mon, 3 Aug 2026 00:28:44 +0000 Subject: [PATCH] chat: put the session list cwd in the url, not a panel channel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Which project group you are looking at is addressable state, so /chat?cwd= 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 --- .../Screens/Dashboard/ChatHistory/index.tsx | 15 ++++++++--- .../src/apps/ChatHistory/ChatDetailPanel.tsx | 7 +++--- .../src/apps/ChatHistory/SessionList.tsx | 25 ++++++++++++++----- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx b/src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx index 1d0bc823..32de66a4 100644 --- a/src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx +++ b/src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx @@ -1,5 +1,5 @@ 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 { WorkspaceView } from 'officerdev'; import { useIsMobile } from 'hooks/useIsMobile'; @@ -38,7 +38,7 @@ type SessionListPageProps = { export const SessionListPage = ({ isNew }: SessionListPageProps) => { const { sessionId } = useParams<{ sessionId: string }>(); const [selected, setSelected] = usePanelChannel('chat:selected-session', null); - const [, setActiveCwd] = usePanelChannel('chat:active-cwd', null); + const [, setSearchParams] = useSearchParams(); const client = useClient(); const selectedRef = useRef(selected); selectedRef.current = selected; @@ -78,7 +78,16 @@ export const SessionListPage = ({ isNew }: SessionListPageProps) => { try { const detail = await client.get(`/chat/sessions/${sessionId}?limit=${CHAT_TAIL}`); 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({ id: sessionId, model: detail.model, diff --git a/src/workspaces/officerdev/src/apps/ChatHistory/ChatDetailPanel.tsx b/src/workspaces/officerdev/src/apps/ChatHistory/ChatDetailPanel.tsx index b241506a..5cb30cb3 100644 --- a/src/workspaces/officerdev/src/apps/ChatHistory/ChatDetailPanel.tsx +++ b/src/workspaces/officerdev/src/apps/ChatHistory/ChatDetailPanel.tsx @@ -1,5 +1,5 @@ import { useCallback } from 'react'; -import { useLocation } from 'react-router'; +import { useLocation, useSearchParams } from 'react-router'; import { Unplug } from 'lucide-react'; import { usePanelChannel } from 'hooks/usePanelChannel'; import { useAuth } from 'hooks/useAuth'; @@ -98,8 +98,9 @@ function NewChat({ resumeSummary, resumeSessionId, initialMessages, total, initi : undefined, }); - // Run the session in the pwd chosen in the Sessions panel; null → backend default (general_chat_sessions). - const [activeCwd] = usePanelChannel('chat:active-cwd', null); + // Run the session in the pwd the URL names; absent → backend default (general_chat_sessions). + const [searchParams] = useSearchParams(); + const activeCwd = searchParams.get('cwd'); const cwd = activeCwd ? { path: activeCwd } : locationState?.cwd; const initialMessage = locationState?.initialMessage diff --git a/src/workspaces/officerdev/src/apps/ChatHistory/SessionList.tsx b/src/workspaces/officerdev/src/apps/ChatHistory/SessionList.tsx index 3c44cbe9..0883eb2b 100644 --- a/src/workspaces/officerdev/src/apps/ChatHistory/SessionList.tsx +++ b/src/workspaces/officerdev/src/apps/ChatHistory/SessionList.tsx @@ -1,5 +1,5 @@ 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 { usePanelChannel } from 'hooks/usePanelChannel'; 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. export const SessionList = () => { const navigate = useNavigate(); - // The working directory the list operates on (null = the default general_chat_sessions dir). - const [activeCwd, setActiveCwd] = usePanelChannel('chat:active-cwd', null); + // The working directory the list operates on (absent = the default general_chat_sessions dir). + // In the URL, not a channel: which project group you're looking at is addressable state, so + // /chat?cwd= 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 [selected, setSelected] = usePanelChannel('chat:selected-session', null); const [editingId, setEditingId] = useState(null); @@ -52,7 +56,15 @@ export const SessionList = () => { { - 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 }} /> @@ -67,7 +79,8 @@ export const SessionList = () => {