Skip to content

Claude voice menu won't open on a real click (self-close race introduced by #492) #494

Description

@rosscado

Symptom (founder-reported, 2026-07-04): after #492, clicking the voice selector trigger on claude.ai does nothing — the popover never appears. No console errors.

Root cause: every open runs toggleMenu → refreshMenu → renderMenu. Both fetches (SpeechSynthesisModule.getVoices voicesCache, PreferenceModule.getVoice in-memory preference map) are warm from the constructor's initial render, so they resolve in microtasks. For a trusted click, microtask checkpoints run between listener invocations — so the rebuild (cleanupExistingElements + recreate) lands between the trigger's click listener and the document-level outside-click listener. That listener then checks this.element.contains(event.target) against a detached old button → treats the opening click as outside → display:none in the same tick the menu opened.

Pre-#492 this was masked by the old base refreshMenu's 100ms sleep, which pushed the rebuild past the click's bubble phase. The refactor removed the sleep and left the Claude dropdown as the one surface still doing wipe-and-rebuild.

Why L4 verification missed it: the probe used page.evaluate(el.click()) — synchronous dispatch, no microtask interleaving between listeners. Only trusted input (CDP Input.dispatchMouseEvent / a real user) reproduces it. Reproduced live with a Playwright page.click probe: {display:"none", ariaExpanded:"false", items:8} with zero console errors.

Fix: make the trigger button and menu container stable nodes — create once, update/refill in place (replaceChildren), never rebuild in renderMenu. This is the refactor's own reconciliation principle applied to the dropdown surface.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions