From 5134501a2f59bcd3d56d6d93ae8659eb237222fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Fri, 7 Aug 2026 02:27:12 +0000 Subject: [PATCH] walkthrough: the tab-name clone check was a guess Co-Authored-By: Claude Opus 5 --- docs/chat-ui-walkthrough.md | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/docs/chat-ui-walkthrough.md b/docs/chat-ui-walkthrough.md index 9d2c4325..e6412abf 100644 --- a/docs/chat-ui-walkthrough.md +++ b/docs/chat-ui-walkthrough.md @@ -425,23 +425,34 @@ 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. +precisely what naming one was meant to prevent. -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 first fix for that was a guess, and the guess was wrong. It read +`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: `; 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 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. -**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"