feat(web): migra Movimento com homem-morto e trava de hardware

- Controle: opt-in de movimento fisico, botao "segure para mover"
  (mouse, toque e teclado), sliders das articulacoes tambem so movem
  enquanto pressionados; velocidade e interpolacao ficam no servidor
- sequencias prontas e posturas salvas: previa so na tela ou execucao
  segurando o botao ate o fim; soltar interrompe na hora
- posturas com nome inline (sem prompt), exclusao confirmada inline,
  exportar/importar; posturas aparecem na paleta (Ctrl+K)
- Calibracao: testes de fim de curso por canal, marcar limites e tabela
  editavel com validacao antes de salvar
- Diagnostico (antigo Servos): ao vivo limitado a 10/s, bloqueado quando
  o braco esta com os motores
- desenho esquematico do braco em SVG com previa animada
- servidor: evento 'motores' tambem ao ligar canais; renovar no alvo
  devolve movendo=false para a sequencia avancar
- aba escondida ou E-Stop soltam o braco e desativam o movimento

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-05 19:56:48 -03:00
co-authored by Claude Opus 5.5
parent 947c0a3127
commit 673dea62e8
10 changed files with 1153 additions and 9 deletions
+27 -1
View File
@@ -150,6 +150,32 @@ def testar(base, server, capturas):
if capturas:
page.screenshot(path=str(Path(capturas) / 'reacoes.png'), full_page=True)
# 6d. braço: opt-in, homem-morto e desligamento pelo prazo ao soltar
hw = server.servos.hw
page.get_by_role('link', name='Movimento').first.click()
segurar = page.get_by_role('button', name='Segure para ir ao alvo')
expect(segurar).to_be_disabled()
page.get_by_role('switch', name='Ativar movimento físico').locator('xpath=..').click()
expect(segurar).to_be_enabled()
hw.calls.clear()
segurar.hover()
page.mouse.down()
page.wait_for_timeout(700)
assert server.servos.active, 'motores deveriam estar ligados enquanto segura'
expect(page.get_by_text('Motores ligados')).to_be_visible(timeout=3000)
page.mouse.up()
page.wait_for_timeout(900)
assert not server.servos.active, 'motores deveriam desligar sozinhos após soltar'
assert any(us for _, us in hw.calls), hw.calls
if capturas:
page.screenshot(path=str(Path(capturas) / 'movimento.png'), full_page=True)
# E-Stop desativa o movimento físico na hora
page.get_by_role('button', name='Parar tudo').click()
expect(page.get_by_role('switch', name='Ativar movimento físico')).to_be_disabled(timeout=3000)
expect(segurar).to_be_disabled()
page.get_by_role('button', name='PARADO · Rearmar').click()
expect(page.get_by_role('button', name='Parar tudo')).to_be_visible(timeout=3000)
# 7. celular: barra inferior e nada de rolagem horizontal
page.set_viewport_size({'width': 400, 'height': 860})
page.get_by_role('link', name='Ao vivo').click()
@@ -170,7 +196,7 @@ def testar(base, server, capturas):
assert not erros, erros
browser.close()
print('OK: token, SSE, dock 1-9, paleta, E-Stop, ajustes otimistas, conversa, memória, reações com desfazer, celular 400px, tema')
print('OK: token, SSE, dock 1-9, paleta, E-Stop, ajustes otimistas, conversa, memória, reações com desfazer, braço com homem-morto, celular 400px, tema')
if __name__ == '__main__':