diff --git a/src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx b/src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx index 32de66a4..4148fc03 100644 --- a/src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx +++ b/src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx @@ -113,7 +113,9 @@ export const SessionListPage = ({ isNew }: SessionListPageProps) => { locked mobilePanelId={mobilePanelId} onMobilePanelChange={(id) => { - if (!id) navigate('/chat', { replace: true }); + // Keep the query string: ?cwd= names the project group the list is showing, so dropping it on + // mobile back sends you to an empty general_chat_sessions instead of the list you came from. + if (!id) navigate({ pathname: '/chat', search: window.location.search }, { replace: true }); }} /> diff --git a/src/workspaces/officerdev/src/apps/Chat/types.ts b/src/workspaces/officerdev/src/apps/Chat/types.ts index 1bc8431c..60d63d2f 100644 --- a/src/workspaces/officerdev/src/apps/Chat/types.ts +++ b/src/workspaces/officerdev/src/apps/Chat/types.ts @@ -68,7 +68,10 @@ export type ServerMessage = parentToolUseId?: string; } | { type: 'tool:result'; toolCallId: string; output: string; isError: boolean; parentToolUseId?: string } - | { type: 'result'; sessionId: string; cost: MessageCost } + // `claudeSessionId` is Claude's OWN transcript uuid, and the only id `/chat/sessions/:id` can resolve. + // `sessionId` is officer's per-connection key (websocket.ts mints it with `msg.sessionId || randomUUID()`), + // which addresses nothing after the socket closes. + | { type: 'result'; sessionId: string; cost: MessageCost; claudeSessionId?: string } | { type: 'sync:messages'; sessionId: string; messages: Message[]; isGenerating: boolean; streamingText: string } | { type: 'error'; message: string; errorCode?: string } | { type: 'stopped' } diff --git a/src/workspaces/officerdev/src/hooks/useChat.ts b/src/workspaces/officerdev/src/hooks/useChat.ts index 58e5bf46..5fa398b8 100644 --- a/src/workspaces/officerdev/src/hooks/useChat.ts +++ b/src/workspaces/officerdev/src/hooks/useChat.ts @@ -219,7 +219,6 @@ export function useChat(initialSessionId?: string, initialModel?: string | null, setSessionId(msg.sessionId); setModel(msg.model); setCwd(msg.cwd); - if (replaceUrl) window.history.replaceState(null, '', `/chat/${msg.sessionId}`); break; case 'assistant:delta': @@ -276,6 +275,15 @@ export function useChat(initialSessionId?: string, initialModel?: string | null, } case 'result': { + // Make the address bar a permalink. This used to run on `session:init` writing `/chat/`, + // which was wrong twice over: that id is officer's per-connection key (`msg.sessionId || + // randomUUID()`), which `/chat/sessions/:id` cannot resolve, and the template dropped + // `location.search` — so a reload lost both the conversation AND the `?cwd=` naming its project + // group, landing you in an empty general_chat_sessions. The transcript uuid is only known once the + // turn reports it, so wait for it, and carry the query string through untouched. + if (replaceUrl && msg.claudeSessionId) { + window.history.replaceState(null, '', `/chat/${msg.claudeSessionId}${window.location.search}`); + } commitStreaming(); setMessages((prev) => [ ...prev,