fix(pi): add the "More voices" door to Pi's Voice settings page (#491 follow-up)#498
Merged
Merged
Conversation
…follow-up) 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. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_012WmjGuAUmFGQBZukitHp4L
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
The founder's door-first fix (#496) restored the "More voices" door in Pi's in-chat menu. The same door was missing from Pi's own Voice settings page (pi.ai/profile/settings) — the founder asked to add it there too. Two stale-DOM causes, both verified live (Layer-4 CDP, headed real Chrome, 2026-07-04):
getVoiceSettingsSelectormatched 0. Pi redesigned the page: the content column driftedmx-auto w-full px-6 py-10→mx-auto w-full max-w-2xl flex-1 px-6 py-3 pt-20, and the card gridgrid grid-cols-2 gap-4→grid grid-cols-1 gap-4 sm:grid-cols-2. Re-anchored to the settings content column (max-w-2xlscopes it off the wider/discover/threadssurfaces) + its direct-child responsive card grid.findAndDecorateVoiceSettingswas gated behind sidebar-decoration in the same added node and searched that node — but the grid lives in the main content, and the settings route has no chat prompt, so thecontent-loadedchain that scans for voice settings never runs there. Un-gated it in the bootstrap to scandocument.bodyonce per mutation batch (idempotent via thegetElementByIdguard; a no-op on ChatGPT — empty selector — and Claude — its stale Pi selector matches 0 there).What changed (door-first)
PiVoiceSettingsnow 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 (settings cards are<button>s here). A sharedbuildMoreVoicesDoor(template, tag)helper backs both Pi doors (in-chat<div>, settings<button>). The door requires a real template and waits for Pi's cards viaobserveSettingsGrid'sMutationObserver, so it can never render unstyled and re-injects if Pi's React drops it.Adversarial review
Ran a 4-lens review workflow on the diff (it touches the shared bootstrap). All four confirmed findings are fixed:
/discover/threads→ tightened withmax-w-2xl, live-verified 0 matches on all three non-settings surfaces.Pi-VoiceSettingsSelector.spec.ts(old literal matches 0, new matches 1, no match on talk/wider grids).MutationObserver.Tests & evidence
npm test(tsc + Jest + Vitest, 203 files / 1786 tests) ✅./profile/settings→ 1 match + door;/discover/threads/talk→ 0 matches, no stray door.Refs #491, #496, #497.
🤖 Generated with Claude Code
https://claude.ai/code/session_012WmjGuAUmFGQBZukitHp4L