fix(ui): device salvo resolvido por nome+hostapi, nao pelo indice do PortAudio - #55
Conversation
…o indice do PortAudio
O indice do PortAudio e apenas a posicao na enumeracao do host API: plugar um
fone USB, parear um Bluetooth ou reinstalar o VB-CABLE renumera tudo. Como a UI
salvava e restaurava a selecao POR INDICE, o Laguna passava a usar outro
dispositivo em silencio — o rotulo do select mostra o nome novo, entao nem a UI
nem o usuario percebem. Com o botao 🔄 re-inicializando o PortAudio, a
renumeracao acontece exatamente no clique que reaplica a selecao salva.
- fillSelect() carrega `name`/`hostapi` no dataset de cada <option> (o `label`
e decorado com API e marcas, nao serve para comparacao);
- savePanelConfig() grava `_devices: {role: {index, name, hostapi}}` ao lado dos
campos de hoje — o indice continua em data[role], nada muda de formato;
- resolveDeviceSelection(saved, options) concentra a regra: casa por
name+hostapi e devolve o indice ATUAL; config antiga (so indice) segue pelo
caminho de hoje; sem match, devolve null;
- restorePanelConfig()/refreshDevices() viram chamadores finos do helper. Sem
match o select fica no placeholder e o painel mostra `warn.device_missing`
(PT/EN) em vez de apontar calado para o device errado.
O payload de /api/start continua sendo o indice int — contrato REST intacto.
Refs #53
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
Parecer do PR Doctor — APROVADA (head
|
Contexto
O índice do PortAudio é só a posição na enumeração do host API — ele renumera quando o usuário liga um fone USB, pareia um Bluetooth, o driver reinicia ou o VB-CABLE é reinstalado. A UI salvava e restaurava a seleção de device por índice (
restorePanelConfigcasandoo.value === val,refreshDevicesidem), então depois de uma renumeração o Laguna passava a usar outro dispositivo, em silêncio: o<option>mostra o nome novo, não há erro, e a tradução some do Discord (ou pior, a captura vira uma saída e gera realimentação). Com a #43 (/api/devices?refresh=1re-inicializando o PortAudio) a renumeração passa a acontecer exatamente no clique do 🔄 — o único momento em que a UI reaplica a seleção salva.O que mudou (só
static/)fillSelect()passaname/hostapipara odatasetde cada<option>. Olabelé decorado com API e marcas (🌊 Laguna,VB-CABLE), então não serve para comparação;/api/devicesjá devolve os campos crus (laguna_devices.py:33-41) — backend intacto.savePanelConfig()grava_devices: {role: {index, name, hostapi}}ao lado dos campos de hoje.data[role]continua sendo o índice: nenhum formato existente mudou.resolveDeviceSelection(saved, options)— função pura que concentra a regra: casa porname+hostapie devolve o índice ATUAL do device; empata homônimos pelo índice salvo; config antiga (só índice) segue o caminho de hoje; sem match devolvenull.restorePanelConfig()/refreshDevices()viram chamadores finos do helper. Sem match, o select fica no placeholder e o painel mostra o aviso novowarn.device_missing(PT e EN,role="status"+aria-live="polite") — melhor que apontar calado para o dispositivo errado. Quando o device volta a aparecer, a identidade salva ainda está nolocalStoragee a seleção é recuperada (não sobrescrevemos a config no caminho do "sumiu").warn.device_missingé re-renderizado no toggle PT/EN, no mesmo padrão deupdateLagunaBadge()/refreshPlaceholders().buildConfig()não mudou: o payload de/api/startcontinua sendoint— contrato REST intacto (AC 5).Gate (resultado real)
python -m compileall -q .→COMPILE_OK;import fase0_poc, laguna_core, laguna_server→IMPORTS_OK(PythonC:\Python313\python.exe).node --check static/app.jsenode --check static/i18n.js→ OK.pytest tests_unit -q→ 48 passed (incluitest_i18n_parity.py, a paridade PT/EN das chaves novas).laguna_core.py/laguna_pipeline.py/fase0_poc.pynem qualquer constante de VAD/latência ou default de modelo.resolveDeviceSelectionfoi exercitada fora do browser (Node, função extraída do próprioapp.js) em 12 casos — renumeração,hostapidesempatando homônimo, device ausente, formato legado (string e número), índice fora do intervalo, entradas vazias, lista de opções vazia e homônimos exatos. Todos passaram. Não foi versionado como teste: o repo não tem runner de JS e criar um seria dependência nova fora do escopo desta issue (fica a sugestão de issue própria).Riscos e limites
finallygarante que o aviso é sempre recalculado — inclusive limpo quando não há config salva.laguna.virtual_out/CABLE); agora, se a identidade salva não existe mais, o select vai para o placeholder + aviso. É o que o AC 2 pede, e é o comportamento previsível — o usuário escolhe conscientemente em vez de o app decidir por ele. Configs antigas (só índice) seguem exatamente como hoje.use_loopbacké restaurado depois decapture_deviceemPERSIST_FIELDS.escutar, então recarregar a página com loopback marcado deixa o select de captura populado com inputs em vez de loopbacks. É um bug pré-existente; o efeito desta PR é que ele deixa de ser silencioso (o aviso aparece, corretamente, dizendo que o device salvo não está na lista). Vale issue própria.Closes #53