fix(pi): restore the "More voices" door in Pi's redesigned in-chat menu (#491)#496
Merged
Conversation
…nu (#491) Pi redesigned its in-chat voice menu (the `div.t-action-m` anchor has been stale since Feb 2024) into a popover it LAZILY BUILDS on expand and tears down on collapse: collapsed it's a pill; expanded, Pi swaps in a `rounded-xl` card whose voice list holds `div.cursor-pointer` rows (no longer `<button>`s). So SayPi's whole in-chat voice surface went dark on Pi — verified live (Layer-4 CDP, 2026-07-04): the old firstChild-`<button>` expansion heuristic watched the wrong depth (a direct-child childList of a shape Pi no longer produces) and never fired, and the `#saypi-voice-menu` element was SayPi's empty fallback div appended OUTSIDE Pi's card. Door-first (founder-scoped): rather than re-inject a full inline voice block into Pi's ephemeral, arbitrary-Tailwind, div-based menu (fragile against Pi's ~yearly redesigns), restore the robust path — the "More voices" door — so Pi users reach the full catalog + previews in the extension's Voices settings. - Observe the audio-controls SUBTREE (Pi's pill↔card swap happens deep, not as a direct child) and, whenever the menu is open, ensure the door is the last row of Pi's live list. Idempotent; re-injects after each rebuild-on-expand. - Anchor on the stable "Toggle voice menu" aria-label → the `rounded-xl` card → the `flex.flex-col.gap-1` list (the load-bearing, most-churn-resistant hooks). - Build the door by CLONING a native row's class list so it renders Pi-native (Pi's own utilities are already compiled — SayPi can't author them itself), plus role=button + Enter/Space for keyboard a11y. Inline SayPi voice rows on Pi's in-chat menu are deferred (follow-up), which keeps #483's Pi ▶ deferred with them. Fixture-backed unit tests reproduce Pi's current DOM; verified live: door renders as the last row (Pi 1-8 + More voices), native-styled, 0 console errors. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_012WmjGuAUmFGQBZukitHp4L
This was referenced Jul 4, 2026
rosscado
added a commit
that referenced
this pull request
Jul 4, 2026
…follow-up) (#498) The founder's #491 door-first fix (#496) restored the door in Pi's in-chat menu; the same door was missing from Pi's own Voice settings page (pi.ai/profile/settings). Two stale-DOM causes, both verified live (Layer-4 CDP, 2026-07-04): 1. getVoiceSettingsSelector matched 0 — Pi redesigned the page: the content column drifted `px-6 py-10` → `mx-auto w-full max-w-2xl px-6 py-3 pt-20` and the card grid `grid-cols-2` → `grid-cols-1 gap-4 sm:grid-cols-2`. Re-anchored to the settings content column (`max-w-2xl` keeps it off the wider /discover /threads surfaces) + its direct-child responsive card grid. 2. Even with a matching selector, decoration never fired: findAndDecorateVoiceSettings was gated behind sidebar-decoration in the same added node AND searched that node — but the grid is in the main content, and the settings route has no chat prompt, so the content-loaded chain never runs there. Un-gated it in the bootstrap to scan document.body once per mutation batch (idempotent via the getElementById guard; a no-op on ChatGPT (empty selector) and Claude (its stale Pi selector matches 0 there)). Door-first: PiVoiceSettings now injects only the "More voices" door (deferring inline SayPi voice rows, companion to the in-chat #497), cloning a native card's class list so it renders Pi-native (cards are `<button>`s here). A shared buildMoreVoicesDoor(template, tag) helper backs both Pi doors (in-chat `<div>`, settings `<button>`); the door requires a real template (waits for Pi's cards via observeSettingsGrid's MutationObserver) so it can never render unstyled. Adversarial-reviewed (4-lens workflow); the four confirmed findings are fixed: empty-grid race (template guard), selector over-match (tightened + live-verified 0 on /discover /threads /talk), missing selector-contract test (Pi-VoiceSettingsSelector.spec.ts), untested observer re-injection (real MutationObserver test). Full gate green (203 files / 1786 tests). Verified live: door renders as the last grid card, native-styled, 0 console errors; no spurious decoration on /discover /threads /talk; #496 in-chat door unaffected. Claude-Session: https://claude.ai/code/session_012WmjGuAUmFGQBZukitHp4L Co-authored-by: Claude Opus 4.8 <[email protected]>
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.
Why
On live pi.ai the SayPi voice block never renders in the in-chat menu (#491) — users see only Pi 1–8. Root cause, confirmed live (Layer-4 CDP, headed real Chrome,
mainbuild): Pi fully redesigned its in-chat voice menu and SayPi's integration has been dark against it (thediv.t-action-manchor is unchanged since Feb 2024).What Pi does now:
.saypi-audio-controlsholds only a pill (two<button>s:Turn voice off,Toggle voice menu) — no voice list exists. On expand Pi swaps in arounded-xlcard with the voice list; on collapse it's destroyed and rebuilt next time.<div>s now, not<button>s (div.cursor-pointer > span "Pi 1").#saypi-voice-menuwas an empty fallback<div>appended outside Pi's card, never getting.expanded.So all three assumptions were stale:
div.t-action-mis gone, the firstChild-<button>expand heuristic watched the wrong depth for a shape Pi no longer produces (never fired), and button-only adoption can't see div rows. Not a #492 regression (A/B-confirmed pre-existing).What changed (door-first, founder-scoped)
Rather than re-inject a full inline voice block into Pi's ephemeral, arbitrary-Tailwind, div-based menu — fragile against Pi's ~yearly redesigns — restore the robust path: the "More voices" door, so Pi users reach the full catalog + ▶ previews in the extension's Voices settings.
Toggle voice menuaria-label → therounded-xlcard → theflex.flex-col.gap-1list.min-h-11/!bg-secondary-defaultitself), plusrole=button+ Enter/Space for keyboard a11y.Deliberately deferred
Inline SayPi voice rows on Pi's in-chat menu are a follow-up (see the tracking issue). This keeps #483 (Pi ▶ on inline rows) deferred with them — there are no inline rows to attach a ▶ to under door-first.
Tests
test/chatbots/PiVoiceMenu-more-voices-door.spec.ts(7 cases) with a fixture that faithfully reproduces Pi's current live DOM (collapsed pill vs. expanded card): door injects into the live list only when expanded, idempotent, last-child placement, native-clone styling, click→openSettings("voices"), Enter/Space a11y, re-injects after rebuild-on-expand.npm test(tsc + Jest + Vitest, 201 files / 1775 tests) ✅Layer-4 real-host evidence (pi.ai, headed real Chrome)
Build tag
66ecd77@fix/491-pi-more-voices-door. Expanded the voice menu: "More voices…" renders as the last row (Pi 1–8 + door, 9 rows), styled native (cursor-pointer+min-h-11,role=button), 0 console errors. Screenshot in the session.Closes #491 (door-first). Refs #483, #492.
🤖 Generated with Claude Code
https://claude.ai/code/session_012WmjGuAUmFGQBZukitHp4L