fix(claude): stop the voice menu self-closing on a real click (#494)#495
Merged
Conversation
Every open runs renderMenu via refreshMenu, whose fetches resolve from warm in-memory caches in microtasks — which trusted clicks let run BETWEEN the trigger's listener and the document outside-click listener. The wipe-and-rebuild render detached the click's event.target mid- bubble, so contains(event.target) read the opening click as "outside" and closed the menu in the tick it opened. (Masked pre-#492 by the old base refreshMenu's 100ms sleep; invisible to synthetic el.click(), which dispatches without microtask checkpoints between listeners.) The trigger and menu container are now stable nodes: created once, updated/refilled in place (replaceChildren), duplicate defense stays in the constructor. Verified on live claude.ai with a trusted-click CDP probe: repro'd {display:none, aria-expanded:false} on the old build, double-open green on this one. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019NoPTGPfcHjnjkcBWZqR6C
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #494 — a regression from #492: on claude.ai, a real click on the voice selector trigger did nothing (menu self-closed in the same tick it opened; zero console errors).
Root cause
Every open runs
toggleMenu → refreshMenu → renderMenu. Both fetches are memory-cached (voices cache + preference map) and warm from the constructor's initial render, so they resolve in microtasks. Trusted clicks run microtask checkpoints between listener invocations, so the wipe-and-rebuild render detached the click'sevent.targetbefore the document-level outside-click listener ran —this.element.contains(event.target)→ false →display:none. Pre-#492 the old baserefreshMenu's 100ms sleep pushed the rebuild past the bubble phase; #492 removed the sleep and left the Claude dropdown as the one surface still rebuilding instead of reconciling.It escaped #492's Layer-4 verification because the probe used
page.evaluate(el.click())— synchronous dispatch, no microtask interleaving. Only trusted input reproduces it.Fix
Apply the refactor's own principle to the dropdown: the trigger button and menu container are stable nodes — created once, then updated/refilled in place (
replaceChildren; newupdateSignInAffordancekeeps the signed-out styling correct across re-renders). Duplicate defense (cleanupExistingElements) stays in the constructor. Bonus: a re-render landing while the menu is open (auth/preference refresh) now refills in place instead of tearing the open menu down.Verification
ClaudeVoiceMenu-open-race.spec.ts): node identity across re-renders, open-menu survival + refill, in-place trigger label/sign-in affordance — all failed on the old render path, pass now.npm test(tsc + Jest + Vitest).page.click→ CDPInput.dispatchMouseEvent): old build reproduces{display:"none", ariaExpanded:"false"}; this build opens cleanly on both rounds{display:"block", items:8}, zero console errors, screenshot shows the full curated menu (shortlist, ▶ previews, HD chips, footnote, door, checkmark).🤖 Generated with Claude Code
https://claude.ai/code/session_019NoPTGPfcHjnjkcBWZqR6C