make the editable tab name survive refreshes

sessionStorage is the per-tab store — separate per tab, survives reload and
navigation, dies with the tab. The route title is derived rather than assigned,
so navigating no longer wipes a name you typed.

Duplicating a tab clones sessionStorage, so a `navigate` that arrives already
holding a name is treated as a clone and drops it; fails soft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 00:59:29 +00:00
co-authored by Claude Opus 5
parent dbd471c32d
commit 7700e8b540
3 changed files with 151 additions and 24 deletions
@@ -11,29 +11,39 @@ import { RescanButton } from '../Rescan/RescanButton';
import { usePageTitle } from '@/state/usePageTitle';
// import { BugReportButton } from '../BugReport/BugReportButton';
// Center title: click to rename this browser tab (per-tab, not persisted).
// Center title: click to rename this browser tab. The name is kept in sessionStorage, so it survives a
// refresh and navigation and dies with the tab — see usePageTitle.
function EditablePageTitle() {
const [title, setTitle] = usePageTitle();
const [editing, setEditing] = useState(false);
const [title, rename] = usePageTitle();
const [draft, setDraft] = useState<string | null>(null);
if (editing) {
// The edit is a draft committed on blur/Enter rather than a live write. Writing every keystroke was
// fine while the title was a plain string, but an empty field now means "use the route name", so
// deleting the last character would snap the input to "Chat" underneath the cursor.
if (draft !== null) {
return (
<input
autoFocus
value={title}
onChange={(ev) => setTitle(ev.target.value)}
onBlur={() => setEditing(false)}
onKeyDown={(ev) => {
if (ev.key === 'Enter' || ev.key === 'Escape') setEditing(false);
value={draft}
onChange={(ev) => setDraft(ev.target.value)}
onBlur={() => {
rename(draft);
setDraft(null);
}}
onKeyDown={(ev) => {
if (ev.key === 'Enter') ev.currentTarget.blur();
if (ev.key === 'Escape') setDraft(null); // discard, keeping whatever the tab was called
}}
aria-label="Tab name"
placeholder={title}
className="pointer-events-auto max-w-[50vw] border-b border-[#1d2724]/40 bg-transparent text-center text-base font-bold text-[#1d2724] outline-none md:text-xl"
/>
);
}
return (
<button
onClick={() => setEditing(true)}
title="Click to rename this tab"
onClick={() => setDraft(title)}
title="Click to rename this tab — clear it to go back to the page name"
className="pointer-events-auto max-w-[50vw] cursor-text truncate text-base font-bold text-[#1d2724] transition-opacity hover:opacity-70 md:text-xl"
>
{title}