🎯 Ask (T229, demo box)
Context:Weather:PersonaId and Context:History:PersonaId are raw number inputs. An operator has no way to know a persona's row id. They should be a dropdown of personas (display name, submit id).
📌 Facts that make this cheap
- These settings hold a persona table row id (
long), not a Kokoro voice id: ContextProviderSettings.cs:22-37, consumed via Orchestrator.ResolveContextPersonaAsync (Orchestrator.cs:1536-1568) → persona's voice, station-voice fallback on missing row. Null/0 = defer to the on-air DJ — the dropdown needs an explicit "On-air DJ (default)" entry mapping to 0.
GET /api/personas already returns PersonaDto { id, name, ... } (PersonaController.cs:52-58) — no new endpoint.
- Per-key control override registry already exists:
SETTING_CONTROL_REGISTRY (SettingsForm.tsx:388-396, SPEC F54.1). Controls submit strings, so a select submitting String(persona.id) needs zero wire/validator changes.
- Precedent:
Station:Voice already renders VoiceSettingControl via the same registry.
🧹 Riders
- No
usePersonaList() client hook exists (persona lists are fetched server-side per page); settings page loader (settings/page.tsx:22-25) doesn't fetch personas today. Add the hook or a third apiGet.
VoiceSettingControl.tsx:47 re-implements the voice fetch instead of using the shared useVoiceList() hook (lib/use-voice-list.ts — SPEC F79.5 says "never a second voice-listing path"). Consolidate while in here — this is Dean's "common service for this kind of stuff": the common services already exist (/api/personas, /api/voices + useVoiceList), the settings page just doesn't use them.
- ⚠️
Tts:Fallback:Voice is a Piper voice id, documentation-only (StationSettingsAllowlist.cs:183-185) — do NOT give it the Kokoro voice dropdown.
✅ Acceptance
- Both PersonaId settings render a persona select with "On-air DJ (default)"; saves round-trip as today.
- No second voice/persona listing path in the client (F79.5 parity).
Refs: #352 (same pattern for Llm:Model), #146 (EngineByKind structured editor precedent).
🎯 Ask (T229, demo box)
Context:Weather:PersonaIdandContext:History:PersonaIdare raw number inputs. An operator has no way to know a persona's row id. They should be a dropdown of personas (displayname, submitid).📌 Facts that make this cheap
long), not a Kokoro voice id:ContextProviderSettings.cs:22-37, consumed viaOrchestrator.ResolveContextPersonaAsync(Orchestrator.cs:1536-1568) → persona's voice, station-voice fallback on missing row. Null/0 = defer to the on-air DJ — the dropdown needs an explicit "On-air DJ (default)" entry mapping to0.GET /api/personasalready returnsPersonaDto { id, name, ... }(PersonaController.cs:52-58) — no new endpoint.SETTING_CONTROL_REGISTRY(SettingsForm.tsx:388-396, SPEC F54.1). Controls submit strings, so a select submittingString(persona.id)needs zero wire/validator changes.Station:Voicealready rendersVoiceSettingControlvia the same registry.🧹 Riders
usePersonaList()client hook exists (persona lists are fetched server-side per page); settings page loader (settings/page.tsx:22-25) doesn't fetch personas today. Add the hook or a thirdapiGet.VoiceSettingControl.tsx:47re-implements the voice fetch instead of using the shareduseVoiceList()hook (lib/use-voice-list.ts— SPEC F79.5 says "never a second voice-listing path"). Consolidate while in here — this is Dean's "common service for this kind of stuff": the common services already exist (/api/personas,/api/voices+useVoiceList), the settings page just doesn't use them.Tts:Fallback:Voiceis a Piper voice id, documentation-only (StationSettingsAllowlist.cs:183-185) — do NOT give it the Kokoro voice dropdown.✅ Acceptance
Refs: #352 (same pattern for Llm:Model), #146 (EngineByKind structured editor precedent).