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:
@@ -397,6 +397,53 @@ server code, not clicked through.
|
||||
|
||||
---
|
||||
|
||||
## 16. The tab you named stays named
|
||||
|
||||
`src/apps/officer-web/state/usePageTitle.ts`, `…/Layout/Header/Header.tsx`
|
||||
|
||||
The title in the middle of the header has been editable for a while, and it renames the browser tab as
|
||||
you type — which is genuinely useful once you have six Officer tabs open and every one of them says
|
||||
"Chat". It just didn't last. Two separate reasons: the name lived only in React Query, so a refresh
|
||||
took it with the page; and `usePageTitleSync` had an effect that reset the title to the route default on
|
||||
every navigation, so clicking anything at all wiped it even without a refresh.
|
||||
|
||||
**There is no tab id in the browser.** `chrome.tabs` gives an extension one, but page scripts are
|
||||
deliberately not allowed to know which tab they are in, or that other tabs exist — it's the same
|
||||
boundary that stops a page enumerating your windows. So there is no id to key the name on.
|
||||
|
||||
There doesn't need to be: **`sessionStorage` *is* the per-tab store.** It's separate per tab, it
|
||||
survives a refresh and in-place navigation, and it's discarded when the tab closes. That is exactly the
|
||||
lifetime a tab name wants. (`localStorage` would be wrong in the obvious way — every tab would share
|
||||
one name, which is the problem, not the fix.)
|
||||
|
||||
So the name is read from `sessionStorage` at module load and mirrored into a `useGlobal` entry; the
|
||||
React Query copy is what re-renders the header, and the storage copy is what survives the reload. The
|
||||
route title is no longer *assigned* to the state, it's **derived** — `label ?? titleForPath(pathname)`.
|
||||
Navigation therefore retitles the tab by itself when you haven't named it, and leaves it alone when you
|
||||
have. Clearing the field is the one way back to the route name, and there's no third state to get stuck
|
||||
in.
|
||||
|
||||
**The one hole is duplicate-tab**, which you use constantly — and duplicating a tab clones its
|
||||
sessionStorage, so the copy would open wearing the original's name. Two tabs called "Platform Arch" is
|
||||
precisely what naming one was meant to prevent. `performance.getEntriesByType('navigation')[0].type`
|
||||
separates the cases: a refresh reports `reload`, a duplicate reports `navigate`. So a `navigate` that
|
||||
arrives already holding a name did not earn it, and the name is dropped. A brand-new tab is `navigate`
|
||||
too but has nothing stored, so it's untouched.
|
||||
|
||||
That check **fails soft on purpose**: if the timing entry is missing, or some browser labels duplication
|
||||
differently, the copy just keeps the name — redundant, not wrong. Nothing else about the tab changes.
|
||||
|
||||
The header input also became a draft committed on blur/Enter rather than a live write. It wrote every
|
||||
keystroke before, which was fine while the title was a plain string; now that an empty field means "use
|
||||
the route name", deleting the last character would have snapped the input to "Chat" under the cursor.
|
||||
Escape discards the draft.
|
||||
|
||||
**Not verified:** the duplicate-tab navigation type, in an actual browser. The rest — that the name
|
||||
survives a refresh and a navigation — follows from sessionStorage's specified behaviour, but the clone
|
||||
heuristic is the part I could only reason about.
|
||||
|
||||
---
|
||||
|
||||
## Things noticed and deliberately left alone
|
||||
|
||||
- **`useChatWebSocket` silently ignores unparseable frames.** That one is intentional and the comment
|
||||
|
||||
Reference in New Issue
Block a user