Skip to content

feat(ui): badge "sem dispositivo virtual" deixa de ser beco sem saída — leva ao passo a passo do rodapé - #67

Merged
caioross merged 1 commit into
mainfrom
auto/issue-65-badge-cta
Aug 5, 2026
Merged

feat(ui): badge "sem dispositivo virtual" deixa de ser beco sem saída — leva ao passo a passo do rodapé#67
caioross merged 1 commit into
mainfrom
auto/issue-65-badge-cta

Conversation

@caioross

@caioross caioross commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Contexto

O badge de dispositivos é o primeiro diagnóstico que o usuário novo vê. Nos estados amarelo (badge.laguna_cable — VB-CABLE detectado, não renomeado) e vermelho (badge.laguna_none — nenhum device virtual) ele dizia o que está errado e escondia como resolver: a receita mora no <details> fechado do rodapé, sem id e sem nenhum link apontando pra lá. Mesmo padrão de "erro visível mas não acionável" já pago na #33 e na #26 — aqui, no ponto de entrada.

O que mudou (só static/)

  • <details> do rodapé ganhou id="setup-guide".
  • O badge continua um <span> informativo; a semântica de botão vive num <button id="setup-guide-cta"> interno, exibido apenas nos estados acionáveis. Isso atende ao critério "no verde não é anunciado como acionável" sem depender de disabled (que arriscaria o tooltip data-tip do estado verde).
  • Um único listener no badge cobre o clique no texto e no CTA (bubbling) e só age quando .is-actionable está presente. Ao acionar: details.open = true, scrollIntoView({behavior:'smooth', block:'start'}) e foco no <summary>.
  • aria-expanded do CTA reflete o <details> nos dois sentidos: sincronizado no mesmo tick ao abrir pelo badge (o evento toggle é assíncrono) e via listener toggle quando o usuário abre/fecha direto no rodapé.
  • O rótulo do badge passou a guardar a chave em data-i18n — mesma convenção já usada em setConnState() para o conn-badge — de modo que o toggle PT/EN reaplique o estado atual.
  • Novas chaves em PT e EN: badge.setup_cta ("como resolver →" / "how to fix →") e aria.setup_cta.
  • CSS: .badge.is-actionable { cursor: pointer } e .badge-cta com reset de <button> (font/cor/borda/fundo) + :focus-visible visível.

Gate (resultado real, rodado na worktree i65)

  • T1compileall -q .COMPILE_OK; import-smoke de fase0_poc, laguna_core, laguna_serverIMPORTS_OK.
  • T3node --check static/app.js static/i18n.jsJS_OK; pytest tests_unit/test_i18n_parity.py -q2 passed.
  • T2 — não se aplica: nenhuma linha de pipeline tocada (diff 100% em static/).

Além do gate, smoke funcional no navegador com o servidor da worktree (encerrado ao fim da rodada, porta 7531 livre), simulando os três estados:

Estado Resultado observado
verde (has_laguna_name) class="badge ok", elemento SPAN, role ausente, CTA hidden, data-tip presente
amarelo (virtual_out presente) badge warn is-actionable, CTA visível; clique → details.open=true, activeElement=SUMMARY, aria-expanded="true"
vermelho (sem device) badge err is-actionable, CTA visível e focável (tabIndex=0), mesmo comportamento no clique
PT→EN rótulo e CTA traduzidos ("how to fix →"), aria-label = "Open the setup step by step"
abrir/fechar o guia no rodapé aria-expanded acompanha (truefalse)

Riscos

Baixos e contidos em static/: nenhum contrato REST/WS tocado, nenhum caminho quente de áudio, nenhuma chamada de rede nova. O único ponto de atenção é visual — o CTA acrescenta ~90px ao badge nos estados amarelo/vermelho, dentro do .badges em flex; em janelas estreitas o header pode quebrar antes do que quebrava.

Classificação HANDBOOK §7.3 (UI/i18n — autonomia total).

Closes #65

…pe (#65)

Nos estados amarelo (VB-CABLE nao renomeado) e vermelho (sem device
virtual) o badge dizia o problema e escondia a solucao num <details>
fechado a tres rolagens de distancia. Agora o badge inteiro e clicavel
nesses estados e revela um CTA "como resolver ->" que abre o guia,
rola ate ele e move o foco pro <summary>.

- <details> do rodape ganha id="setup-guide"
- badge segue <span> informativo; a semantica de botao vive num
  <button> interno, so visivel nos estados acionaveis (no verde nao ha
  role de botao nem cursor de acao, e o tooltip continua intacto)
- aria-expanded do CTA reflete o <details> nos dois sentidos (abrir
  pelo badge e abrir/fechar direto no rodape)
- rotulo do badge passa a guardar a chave em data-i18n, mesma
  convencao do conn-badge, para o toggle PT/EN reaplicar o estado
- novas chaves badge.setup_cta e aria.setup_cta em PT e EN
@caioross

caioross commented Aug 5, 2026

Copy link
Copy Markdown
Owner Author

Parecer do PR Doctor — aprovada

Classificação: HANDBOOK §7.3 (autonomia total) — diff 100% em static/, sem backend, sem contrato REST/WS, sem caminho quente de áudio, sem dependência nova. Confirmado arquivo a arquivo: app.js, i18n.js, index.html, style.css. Não é área de quórum.

Acceptance criteria da #65 — todos atendidos, por isso o Closes #65 procede (não é fatia parcial):

  • vermelho/amarelo → o listener em #laguna-badge só age com .is-actionable, e openSetupGuide() faz open = true + scrollIntoView + foco no <summary>, sem reload;
  • verde → segue <span> informativo, sem role, sem is-actionable, CTA hidden; o data-tip-i18n está preservado no elemento (o className é reescrito, os atributos não), então o tooltip continua;
  • teclado → o CTA é <button> real: alcançável por Tab, Enter/Espaço disparam click nativo que borbulha para o listener do badge. aria-expanded sincronizado nos dois sentidos — no mesmo tick ao abrir pelo badge (o evento toggle é assíncrono) e via listener toggle quando o usuário abre/fecha no rodapé;
  • i18n → badge.setup_cta e aria.setup_cta em PT e EN.

Dois pontos que verifiquei por serem os que quebrariam em silêncio:

  1. applyI18n (static/i18n.js:319) faz el.textContent = T(key) em todo [data-i18n]. O data-i18n dinâmico do rótulo funciona porque o CTA é irmão do [data-role="laguna-label"], não filho — reescrever o texto do rótulo não destrói o botão. Mesma convenção já usada no conn-badge.
  2. Nenhuma regra de style.cssdisplay ao .badge-cta, então o atributo hidden (display:none do UA) de fato esconde o CTA no estado verde. .badges é flex, mas isso vale para os badges, não para o botão interno.

Gate rodado por mim na worktree (C:\Python313\python.exe), não só o relatado: T1 COMPILE_OK + IMPORTS_OK; T3 node --check nos dois JS → JS_OK + paridade PT/EN pelo teste versionado; pytest tests_unit/ -q → 54 passed. T2 não se aplica (nada de pipeline).

Gate também sobre a união com a main nova (a #64 entrou entre a abertura desta PR e este parecer, e as duas tocam static/i18n.js): git merge origin/main local → auto-merge limpo em i18n.js, COMPILE_OK + JS_OK + 61 passed, paridade PT/EN inclusa. CI verde, mergeable: CLEAN.

Ressalva (não bloqueia): o CTA acrescenta largura ao badge nos estados amarelo/vermelho — em janela estreita o header quebra antes do que quebrava. O próprio corpo da PR declara isso; é visual, reversível, e só aparece justamente quando o usuário precisa da instrução.

Squash-merge.

@caioross
caioross merged commit 9f4b852 into main Aug 5, 2026
2 checks passed
@caioross
caioross deleted the auto/issue-65-badge-cta branch August 5, 2026 05:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ui: badge "sem dispositivo virtual" é beco sem saída — diz o problema e esconde o passo a passo num <details> fechado do rodapé

1 participant