header: per-route page titles + a centered, click-to-rename tab title

Each dashboard route now sets its own document.title (route→title map in
usePageTitleSync, mounted in DashboardLayout). The title also renders centered in
the top header; clicking it edits inline and updates the browser tab live —
per-tab only, reset on navigation/refresh (not persisted).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-24 13:53:37 +00:00
co-authored by Claude Opus 4.8
parent 85b6a381e8
commit f4cf20d378
3 changed files with 95 additions and 1 deletions
@@ -3,6 +3,7 @@ import { Background } from './Background';
import { Header } from './Header';
import { Dock, ALL_DOCK_ITEMS, DEFAULT_DOCK_PATHS } from './Dock';
import { useIsTouch } from './useIsTouch';
import { usePageTitleSync } from '@/state/usePageTitle';
type DashboardLayoutProps = {
children?: React.ReactNode;
@@ -10,6 +11,7 @@ type DashboardLayoutProps = {
export function DashboardLayout({ children }: DashboardLayoutProps) {
const { items: visibleItems } = useDock(ALL_DOCK_ITEMS, DEFAULT_DOCK_PATHS);
const isTouch = useIsTouch();
usePageTitleSync();
return (
<div className="relative overflow-hidden h-dvh outline-none inset-0">
@@ -8,8 +8,39 @@ import { useIsTouch } from '../useIsTouch';
import { UserMenu } from './UserMenu';
import { JobsIndicator } from './JobsIndicator';
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).
function EditablePageTitle() {
const [title, setTitle] = usePageTitle();
const [editing, setEditing] = useState(false);
if (editing) {
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);
}}
className="pointer-events-auto max-w-[50vw] border-b border-white/50 bg-transparent text-center text-sm font-semibold text-white outline-none md:text-base"
/>
);
}
return (
<button
onClick={() => setEditing(true)}
title="Click to rename this tab"
className="pointer-events-auto max-w-[50vw] cursor-text truncate text-sm font-semibold text-white/90 transition-colors hover:text-white md:text-base"
>
{title}
</button>
);
}
type HeaderProps = {
dockItems?: DockItem[];
};
@@ -24,9 +55,13 @@ export function Header({ dockItems }: HeaderProps) {
return (
<header className="fixed z-10 w-full">
<div
className="shrink-0 border-b backdrop-blur-xl shadow-lg px-3 py-2 md:px-6 md:py-3 flex items-center justify-between"
className="relative shrink-0 border-b backdrop-blur-xl shadow-lg px-3 py-2 md:px-6 md:py-3 flex items-center justify-between"
style={{ backgroundColor: 'rgba(255, 255, 255, 0.25)', borderColor: 'rgba(255, 255, 255, 0.35)' }}
>
{/* Centered, editable page title */}
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<EditablePageTitle />
</div>
<div className="flex items-center gap-2">
{isTouch && dockItems && (
<button onClick={() => setOpen(true)} className="p-1.5 rounded-md hover:bg-white/20 transition-colors">