walkthrough: the tab-name clone check was a guess

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 02:27:12 +00:00
co-authored by Claude Opus 5
parent ff5095e71f
commit 5134501a2f
+21 -10
View File
@@ -425,23 +425,34 @@ in.
**The one hole is duplicate-tab**, which you use constantly — and duplicating a tab clones its **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 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` precisely what naming one was meant to prevent.
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 The first fix for that was a guess, and the guess was wrong. It read
differently, the copy just keeps the name — redundant, not wrong. Nothing else about the tab changes. `performance.getEntriesByType('navigation')[0].type`, on the theory that a refresh reports `reload` and
a duplicate reports `navigate`, so a `navigate` arriving already holding a name did not earn it. Only
half of that is true: **`reload` means F5 or Ctrl-R and nothing else.** Pressing Enter in the address
bar is `navigate`. Following a link back into the app is `navigate`. Re-opening the URL after the server
was down — which is how you come back from every `pm2 restart` — is `navigate`. All of them threw the
name away, which read as "sessionStorage isn't surviving restarts". sessionStorage was fine; we were
deleting it on arrival.
So it asks now instead of guessing. Each tab stores an id beside its name, and a duplicate is a tab
whose id is **still held by a tab that is alive** — which the original can just say, over a
`BroadcastChannel`. The new document broadcasts `claim: <id>`; any live tab holding that id answers
`taken`; on hearing that, the copy mints a fresh id and gives up the name. A refresh has nobody to
answer, because the old document is destroyed before the new one's scripts run. A brand-new tab has no
id at all, so it can't be a copy of anything and never asks.
The answer arrives a beat late, so the header can show the inherited name for a frame before it clears.
That's the price of asking a real question instead of reading a tea leaf, and it's the right trade: this
still **fails soft toward keeping the name** — no `BroadcastChannel`, or nobody answering, means no
clone detected. A copy keeping a name is redundant. Losing a name you typed is a bug.
The header input also became a draft committed on blur/Enter rather than a live write. It wrote every 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 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. the route name", deleting the last character would have snapped the input to "Chat" under the cursor.
Escape discards the draft. Escape discards the draft.
**Verified in the browser.** The clone heuristic was the one part I could only reason about; you tested
it and it behaves as designed — a duplicated tab keeps the _page_ (React Query cache and all) but not
the _name_, and the two tabs then diverge.
--- ---
## 17. Stop means stop, not "Claude Code returned an error" ## 17. Stop means stop, not "Claude Code returned an error"