the permalink goes through the router, so the open chat learns its own name
Third attempt at this bug, and the first two were fixes to the wrong component. The /chat screen renders
ChatDetailPanel, not ChatHeader — so invalidating the sessions list and correcting which id the panel records
were both real improvements to a surface that is not the one showing "New chat".
The actual cause is one line. The permalink was written with `window.history.replaceState`, which changes the
address bar and tells react-router nothing. On that screen BOTH the title and the rename control come from
`useParams().sessionId` — see the note in DetailBar about renaming the chain's head rather than the tail — so
the router kept reporting no session, the title stayed "New chat", and rename stayed absent. Navigating away
and back forced the router to re-read the location, which is why that appeared to be the cure and why the fix
looked like it belonged in a list.
Now a `navigate(..., { replace: true })`. Replace rather than push, so Back still leaves the conversation
instead of stepping through the same one under a succession of names.
Worth recording for the next one: three separate "the UI did not update" bugs today were all a URL or an id
not reaching the thing that reads it — two of them the officer-key/transcript-id pair, this one the router.
`useQueryState` still writes `history.replaceState` directly; same trap, different hook, left alone rather
than widened into this change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useChatWebSocket } from 'hooks/useChatWebSocket';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { useSettings } from 'state/useSettings';
|
||||
@@ -44,11 +45,23 @@ type UsePiChatOptions = {
|
||||
* contradict it — which is what a hand-edited or stale `?cwd=` used to do. Anything else in the query
|
||||
* string is left alone.
|
||||
*/
|
||||
function writePermalink(claudeSessionId: string): void {
|
||||
/**
|
||||
* The permalink, as a ROUTER navigation rather than a raw history write.
|
||||
*
|
||||
* It was `window.history.replaceState`, which changes the address bar and tells react-router nothing. Every
|
||||
* consumer that reads the URL through the router therefore never saw it: on the /chat screen the title and
|
||||
* the rename control both come from `useParams().sessionId` (see `ChatDetailPanel`), so a new conversation
|
||||
* stayed "New chat" and could not be renamed until something else forced the router to re-read the
|
||||
* location — which is exactly what navigating away and back did.
|
||||
*
|
||||
* `replace`, not push, so the browser Back button still leaves the conversation rather than stepping through
|
||||
* the same one under a different name.
|
||||
*/
|
||||
function permalinkTo(claudeSessionId: string): { pathname: string; search: string } {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
params.delete('cwd');
|
||||
const search = params.toString();
|
||||
window.history.replaceState(null, '', `/chat/${claudeSessionId}${search ? `?${search}` : ''}`);
|
||||
return { pathname: `/chat/${claudeSessionId}`, search: search ? `?${search}` : '' };
|
||||
}
|
||||
|
||||
export function useChat(initialSessionId?: string, initialModel?: string | null, options?: UsePiChatOptions) {
|
||||
@@ -202,6 +215,21 @@ export function useChat(initialSessionId?: string, initialModel?: string | null,
|
||||
|
||||
const client = useClient();
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
|
||||
/*
|
||||
Written through the router so `useParams` updates. A raw `history.replaceState` moves the address bar and
|
||||
leaves react-router believing the location is unchanged — see `permalinkTo`.
|
||||
|
||||
In a ref because the message handler is re-created per render and closes over whatever it had; a stable
|
||||
callback keeps the socket's copy valid.
|
||||
*/
|
||||
const writePermalink = useCallback(
|
||||
(claudeSessionId: string) => {
|
||||
navigate(permalinkTo(claudeSessionId), { replace: true });
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
/*
|
||||
Tell the session LISTS that this conversation exists.
|
||||
|
||||
Reference in New Issue
Block a user