feat(voices): say which voice is actually speaking (#599, #600) - #601
Merged
Conversation
Two surfaces that both left the user to guess. Settings → Voices had one statement about the present — `Your voice: X ↗` — and it only ever appeared when a SayPi voice was selected. Select nothing and the bar said nothing, on a page whose whole subject is what you sound like. What speaks then differs by host, so the missing line is two: an assistant that ships its own voices answers in one of them, and one that doesn't stays silent. `hasBuiltins` already told the two apart; only the rendering was missing. The same line now also covers a host built-in being the current voice, which used to suppress the control and show nothing. pi.ai's own Voice settings is where Pi now sends people to choose a voice (#573), and it went on highlighting `Pi 4` while Shimmer did the talking. SayPi's only mark there was its "More voices" door. It now states what is speaking above the grid and dims the native cards — dimmed, not disabled: that card is a real preference and is exactly what would speak with the SayPi voice switched off, so it should read as standing by, not as gone. The notice lives inside the grid as a full-width first cell rather than as a sibling above it, so the observer that already heals the door from Pi's re-renders heals this too. Fail-first on both: 3 control-bar specs and 3 Pi-grid specs fail on main. Copy is pinned by the locale-backed contract spec and translated into all 31 locales (translate-missing.py; every placeholder verified). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgkAqzyjN5xtxR5fKxRPR2
#600) Layer-4 verification found each of them saying the wrong thing on a real page, for reasons no unit test could have raised — both fixtures encoded an assumption production doesn't hold. #599 read `hasBuiltins` to decide whether the host answers in its own voice. The API does not mark Pi's built-ins `default`, so that signal is false even on Pi, and the bar told live users "No voice — replies aren't read aloud" about an assistant that was about to speak. It now reads a declared `hasOwnVoice` on the host, pinned by a spec against `audioProviders.getDefaultForChatbot` so the studio can't drift from the audio layer's own answer to the same question. #600's notice never painted at all: it needs the stored voice, and nothing fetched it on this surface — the base renders on an auth or preference CHANGE, and an ordinary page load is neither. Measured live as door present, notice missing. The surface now reads the preference when it is built. Both corrections come with the test that would have caught them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgkAqzyjN5xtxR5fKxRPR2
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 #599. Fixes #600.
Two surfaces that both left the user to guess what they sound like — the pair the founder hit alongside #597.
Settings → Voices (#599)
The bar's one statement about the present,
Your voice: Shimmer ↗, only ever appeared when a SayPi voice was selected. Select nothing and it said nothing at all — on the page whose entire subject is what you sound like.What speaks then depends on the host, so the missing line is really two sentences:
Your voice: Pi's ownNo voice — replies aren't read aloudhasBuiltins(voices the API marksdefault, which the rail lists nowhere) already distinguished the two in the view model; only the rendering was missing. The same line also covers the case where a host built-in is the current voice, which previously suppressed the jump control and showed nothing in its place.pi.ai's own Voice settings (#600)
This is the surface Pi now sends people to for voice choice (#573), and it went on highlighting
Pi 4while Shimmer did the talking. SayPi's only mark on the page was its "More voices" door.It now states what is speaking above the grid —
Say, Pi is speaking as Shimmer — the voices below aren't in use.— and dims the native cards.Dimmed, not disabled, deliberately: the highlighted card is a real preference and is exactly what would speak if the SayPi voice were switched off. It should read as standing by, not as gone.
The notice is injected as a full-width first cell inside the grid rather than as a sibling above it, so the MutationObserver that already heals the door from Pi's React re-renders heals this too — a sibling sits outside what that observer watches and would be dropped for good.
Tests
Fail-first on both: 3 control-bar specs and 3 Pi-grid specs fail on
main.The Pi-grid spec mocks
src/i18nto renderkey:substitutions, because the sharedchrome.i18nmock returns the bare key and would hide the one thing the notice must get right — the voice's name reaching the sentence. The English wording itself stays pinned by the locale-backed copy contract.Copy translated into all 31 locales with
tools/i18n/translate-missing.py; every placeholder verified,i18n-validateclean.tsc --noEmit, Jest and 2676 Vitest tests green.Note
Independent of #598 (the pipeline fix) — different files, no conflict — but they answer the same report: #598 makes "In use" true again, and this makes the page honest when nothing of ours is in use at all.
🤖 Generated with Claude Code
https://claude.ai/code/session_01KgkAqzyjN5xtxR5fKxRPR2