Contexto
O botão 🔄 Atualizar dispositivos (entregue na #23) promete literalmente, no tooltip:
"Re-detecta os dispositivos de áudio sem recarregar a página. Use ao conectar um fone ou renomear o CABLE depois de abrir o painel." (static/i18n.js:17)
Só que a promessa não se sustenta no backend. O caminho é:
refreshDevices() (static/app.js:136) → loadDevices() (static/app.js:110) → GET /api/devices (laguna_server.py:74) → list_devices() (laguna_core.py:712) → sd.query_devices().
O PortAudio enumera os devices uma única vez, no Pa_Initialize (que o sounddevice dispara no primeiro uso do processo) e serve a lista de um cache estático. Chamar sd.query_devices() de novo devolve exatamente a mesma lista congelada. Ou seja: fone plugado depois, VB-CABLE instalado depois, ou "CABLE Output" renomeado para Laguna Translator Mic depois — nada disso aparece. O botão gira o spinner, repopula os selects com os mesmos dados e o usuário conclui que o hardware dele é que está errado.
Sintoma cruel: o passo a passo de setup manda renomear o CABLE — e é justamente o momento em que a lista precisa mudar. A única saÃda real hoje é reiniciar o servidor (recarregar a página não basta, o processo é o mesmo).
Ninguém re-inicializa o PortAudio no repo: grep -rn "_terminate\|_initialize" --include=*.py . só acha comentários e stubs de teste.
Proposta
Em /api/devices, aceitar um refresh explÃcito que re-inicialize o PortAudio antes de enumerar:
sd._terminate()
sd._initialize()
Guarda obrigatória — não re-inicializar com worker rodando. _terminate() derruba os streams abertos e, pior, os Ãndices dos devices são reatribuÃdos na nova enumeração: um worker vivo guarda capture_device/output_devices como int (laguna_core.py:54-56) e passaria a apontar para outro hardware silenciosamente. O servidor já sabe quem está rodando (_workers, laguna_server.py:37) — basta pular o re-init (ou recusar) quando _workers não estiver vazio, e a UI explicar "pare as direções para re-detectar".
Acceptance criteria
Abordagem
Menor diff que resolve: parâmetro opcional no endpoint (ex.: GET /api/devices?refresh=1) — o boot continua chamando a versão barata, e só o clique no 🔄 pede o re-init. Isso evita pagar Pa_Terminate/Pa_Initialize (centenas de ms no WASAPI) em todo carregamento de página.
Escopo / autonomia
Toca laguna_server.py (contrato REST) → área de quórum, HANDBOOK §7.2. Gate: T1 + pytest tests_unit/ + T3 se mexer em static/.
Contexto
O botão 🔄 Atualizar dispositivos (entregue na #23) promete literalmente, no tooltip:
Só que a promessa não se sustenta no backend. O caminho é:
refreshDevices()(static/app.js:136) →loadDevices()(static/app.js:110) →GET /api/devices(laguna_server.py:74) →list_devices()(laguna_core.py:712) →sd.query_devices().O PortAudio enumera os devices uma única vez, no
Pa_Initialize(que osounddevicedispara no primeiro uso do processo) e serve a lista de um cache estático. Chamarsd.query_devices()de novo devolve exatamente a mesma lista congelada. Ou seja: fone plugado depois, VB-CABLE instalado depois, ou "CABLE Output" renomeado paraLaguna Translator Micdepois — nada disso aparece. O botão gira o spinner, repopula os selects com os mesmos dados e o usuário conclui que o hardware dele é que está errado.Sintoma cruel: o passo a passo de setup manda renomear o CABLE — e é justamente o momento em que a lista precisa mudar. A única saÃda real hoje é reiniciar o servidor (recarregar a página não basta, o processo é o mesmo).
Ninguém re-inicializa o PortAudio no repo:
grep -rn "_terminate\|_initialize" --include=*.py .só acha comentários e stubs de teste.Proposta
Em
/api/devices, aceitar um refresh explÃcito que re-inicialize o PortAudio antes de enumerar:Guarda obrigatória — não re-inicializar com worker rodando.
_terminate()derruba os streams abertos e, pior, os Ãndices dos devices são reatribuÃdos na nova enumeração: um worker vivo guardacapture_device/output_devicescomoint(laguna_core.py:54-56) e passaria a apontar para outro hardware silenciosamente. O servidor já sabe quem está rodando (_workers,laguna_server.py:37) — basta pular o re-init (ou recusar) quando_workersnão estiver vazio, e a UI explicar "pare as direções para re-detectar".Acceptance criteria
sd._terminate/sd._initializesão API privada dosounddevice: acesso defensivo (getattr+try/except) com fallback para a enumeração simples — versão sem esses sÃmbolos degrada, não quebra o endpoint (a ui: falha do /api/devices no boot deixa a UI morta e muda (WebSocket nunca conecta, nenhum erro visÃvel) #33 já ensinou que/api/devicesquebrado mata o boot da UI).tests_unit/com o stub desd(padrão detests_unit/test_validate_config.py): (a) sem workers → chama re-init e devolve a lista nova; (b) com worker registrado → não chama re-init; (c)sdsem_terminate→ devolve a lista mesmo assim, sem exceção.static/i18n.jse o rodapé de setup).Abordagem
Menor diff que resolve: parâmetro opcional no endpoint (ex.:
GET /api/devices?refresh=1) — o boot continua chamando a versão barata, e só o clique no 🔄 pede o re-init. Isso evita pagarPa_Terminate/Pa_Initialize(centenas de ms no WASAPI) em todo carregamento de página.Escopo / autonomia
Toca
laguna_server.py(contrato REST) → área de quórum, HANDBOOK §7.2. Gate: T1 +pytest tests_unit/+ T3 se mexer emstatic/.