You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Between 2026-07-24 21:05 UTC and 2026-07-27 08:45 UTC the ElevenLabs subscription was past_due and every TTS call returned 401. For 36 hours the API kept serving Paola — an ElevenLabs voice — as recommended and as half the featured HD pair, so every new user was handed a voice that could not speak.
saypi-api #321 (merged + deployed) fixes the steering half server-side: /voices now carries a live availability per voice, and a hard-down provider is excluded from featured/recommended. Because PreferenceModule.maybeAdoptDefaultVoice (src/prefs/PreferenceModule.ts:917) already adopts the server's recommended voice, that path is fixed with no client change — a fresh install during an outage now lands on a working voice automatically.
What the server cannot fix is the user who already has a dead voice saved. Their stored id still resolves, /speak still routes to the dead provider, and they get an error with no path out. Rescuing them needs the client, because server-side substitution at synthesis time is the cross-provider failover the API deliberately rejects (a voice must not switch mid-stream, mid-conversation).
sibling_id was designed for exactly this and has been shipped and unused since #293.
Current state
SpeechModel.ts:283-292 is candid about it: "featured/section/deprecated are consumed today; recommended, sibling_id, language and chars_per_minute are preserved for later phases." That's now partly out of date — recommended and sample_url are both consumed. Verified across src/:
1. availability — add to the type and act on it. Values: "available" | "degraded" | "unavailable", absent/null when unknown. Suggested treatment:
unavailable — don't offer it in the menu, but keep rendering it if it's the user's current selection (the grandfathering invariant already in VoiceCuration.visibleCatalog). Silently vanishing the voice someone is using is worse than showing it with a note.
degraded — no menu change; at most a subtle affordance. The server deliberately does not de-feature these, so the client shouldn't either.
absent/null — treat exactly as today. The server sends null whenever health is unknown, on purpose: never infer trouble from silence.
2. sibling_id — the downshift rescue. When the current voice is unavailable and has a sibling_id, offer the sibling. Worth deciding explicitly: automatic switch with a toast, or a prompt? Automatic gets the user talking again immediately; a prompt respects that voice choice is personal. My weak preference is a prompt on first occurrence, remembered thereafter — but this is a UX call, not a technical one.
3. chars_per_minute — absolute duration figures. Lowest priority, unblocks "≈N min of speech" instead of relative framing. Note the server still sends null (unmeasured), so it stays a no-op until that data lands.
Two pre-existing mismatches worth folding in
Both found while tracing the above; they cause silent divergence between what the server curates and what users see.
Menu cap.CLAUDE_MENU_CAP/PI_MENU_CAP are 4 (VoiceCuration.ts:28-29) while the server caps at pi=5 / claude=6. The server marks 5 voices featured for Pi and the client renders 4, so the 5th is never visible — today that's Marin. Either the client cap rises or the server's should drop; they shouldn't disagree silently.
EVERYDAY_RANK drift. The client's fallback list (VoiceCuration.ts:45) still lacks cedar and marin, added server-side in saypi-api test(layer4): detect stale dev builds (build-identity stamp + stalled-hot-reload warning) #312. Only affects the manifest-blind fallback path, so it's latent rather than broken — but it's the path that runs if the server ever stops sending featured.
Testing
The scenario to pin: catalog where the user's stored voice is unavailable and has a sibling_id → menu offers the sibling, stored voice still rendered, no empty menu. Plus the fail-open case — every voice availability: null must produce today's exact behaviour.
Filed from saypi-api after shipping #321. Server-side design doc: docs/superpowers/specs/2026-07-27-voices-availability-design.md.
Why now
Between 2026-07-24 21:05 UTC and 2026-07-27 08:45 UTC the ElevenLabs subscription was
past_dueand every TTS call returned 401. For 36 hours the API kept serving Paola — an ElevenLabs voice — asrecommendedand as half the featured HD pair, so every new user was handed a voice that could not speak.saypi-api #321 (merged + deployed) fixes the steering half server-side:
/voicesnow carries a liveavailabilityper voice, and a hard-down provider is excluded fromfeatured/recommended. BecausePreferenceModule.maybeAdoptDefaultVoice(src/prefs/PreferenceModule.ts:917) already adopts the server'srecommendedvoice, that path is fixed with no client change — a fresh install during an outage now lands on a working voice automatically.What the server cannot fix is the user who already has a dead voice saved. Their stored id still resolves,
/speakstill routes to the dead provider, and they get an error with no path out. Rescuing them needs the client, because server-side substitution at synthesis time is the cross-provider failover the API deliberately rejects (a voice must not switch mid-stream, mid-conversation).sibling_idwas designed for exactly this and has been shipped and unused since #293.Current state
SpeechModel.ts:283-292is candid about it: "featured/section/deprecated are consumed today; recommended, sibling_id, language and chars_per_minute are preserved for later phases." That's now partly out of date —recommendedandsample_urlare both consumed. Verified acrosssrc/:recommendedsample_urlsibling_idchars_per_minuteavailabilityProposed work
1.
availability— add to the type and act on it. Values:"available" | "degraded" | "unavailable", absent/null when unknown. Suggested treatment:unavailable— don't offer it in the menu, but keep rendering it if it's the user's current selection (the grandfathering invariant already inVoiceCuration.visibleCatalog). Silently vanishing the voice someone is using is worse than showing it with a note.degraded— no menu change; at most a subtle affordance. The server deliberately does not de-feature these, so the client shouldn't either.2.
sibling_id— the downshift rescue. When the current voice isunavailableand has asibling_id, offer the sibling. Worth deciding explicitly: automatic switch with a toast, or a prompt? Automatic gets the user talking again immediately; a prompt respects that voice choice is personal. My weak preference is a prompt on first occurrence, remembered thereafter — but this is a UX call, not a technical one.3.
chars_per_minute— absolute duration figures. Lowest priority, unblocks "≈N min of speech" instead of relative framing. Note the server still sends null (unmeasured), so it stays a no-op until that data lands.Two pre-existing mismatches worth folding in
Both found while tracing the above; they cause silent divergence between what the server curates and what users see.
CLAUDE_MENU_CAP/PI_MENU_CAPare 4 (VoiceCuration.ts:28-29) while the server caps at pi=5 / claude=6. The server marks 5 voicesfeaturedfor Pi and the client renders 4, so the 5th is never visible — today that's Marin. Either the client cap rises or the server's should drop; they shouldn't disagree silently.EVERYDAY_RANKdrift. The client's fallback list (VoiceCuration.ts:45) still lackscedarandmarin, added server-side in saypi-api test(layer4): detect stale dev builds (build-identity stamp + stalled-hot-reload warning) #312. Only affects the manifest-blind fallback path, so it's latent rather than broken — but it's the path that runs if the server ever stops sendingfeatured.Testing
The scenario to pin: catalog where the user's stored voice is
unavailableand has asibling_id→ menu offers the sibling, stored voice still rendered, no empty menu. Plus the fail-open case — every voiceavailability: nullmust produce today's exact behaviour.Filed from saypi-api after shipping #321. Server-side design doc:
docs/superpowers/specs/2026-07-27-voices-availability-design.md.