Repository navigation
fix(startup): a primeira execução travava num seletor de idioma invisível - #563
Merged
Merged
Conversation
…e sem pista Relato de um teste de instalacao do zero (`C:\DRerio\DRerio-LogAI`): o `install.bat` correu inteiro, mas o duplo-clique no icone nao fazia absolutamente nada. As propriedades do atalho estavam corretas -- `.venv\Scripts\pythonw.exe -m zebtrack` -- e rodar exatamente isso sob `pythonw` numa maquina ja configurada chega ao `mainloop` normalmente. Ou seja: o atalho nao e o defeito. O defeito e nao haver como saber qual e. `run_app` converte falhas em dialogo, mas **so a partir do ponto em que a raiz Tk existe**. O parsing de argumentos, o `_setup_logging` e o proprio `tk_module.Tk()` rodam antes disso, e uma falha do proprio handler de erro tambem escapa. Nesses casos o traceback vai para o `sys.excepthook` padrao, que escreve em `sys.stderr` -- e sob `pythonw`, escolhido justamente para nao deixar um console atras da janela, `sys.stderr` e `None`. O traceback vai para lugar nenhum e o processo morre calado. Para quem tem terminal isso e um contratempo. Para o operador que so tem o icone, e o pior desfecho possivel: nada aconteceu, e nada explica por que. `__main__.main()` agora envolve `run_app` e, **quando nao ha stderr**, mostra o traceback num message box junto com o caminho de `logs/analysis.log` e o comando de terminal que reproduz o erro. Com console e no-op deliberado: ali o traceback do proprio Python e melhor que um modal, e a suite afirma sobre ele. `SystemExit` passa direto sem ser reportado -- o caminho de pesos ausentes sai assim depois de ja ter mostrado um dialogo que nomeia a pasta e o remedio, e um segundo modal generico so pioraria. Se o proprio Tk for o que esta quebrado, a mensagem cai no log, e o erro ORIGINAL e o que chega ao chamador, nao o do reporter. Verificado sob `pythonw` de verdade, iniciado destacado como o Explorer faz (`stderr=None` confirmado no processo): o dialogo e montado com o traceback completo e o caminho do log. Os quatro testes novos discriminam -- conferido revertendo cada metade: remover a rede faz falhar o dialogo; remover a guarda de console faz o modal aparecer para quem tem terminal; remover o repasse de `SystemExit` faz o caminho de pesos ausentes ganhar um segundo dialogo. Suite rapida: 6734 passed, 5 skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ivel
Teste de instalacao do zero, pelo terminal, parava aqui:
logging.session.start
logging.configured cwd=C:\DRerio\DRerio-LogAI
e nunca mais saia dali. Pelo icone, o duplo-clique nao fazia nada.
O que vem depois dessa linha e `Tk()`, `root.withdraw()` e o seletor de idioma
de primeira execucao. Ele chamava `dialog.transient(root)` -- e **uma janela
transient herda o estado mapeado do master**. Com o root withdrawn a janela
tambem fica withdrawn, e nem `deiconify()` nem `lift()` revertem. Medido no
Windows:
transient only viewable=False state=withdrawn 1x1
transient + deiconify viewable=False state=withdrawn 1x1
transient + deiconify + lift viewable=False state=withdrawn 1x1
no transient viewable=True state=normal 167x61
O chooser era criado, nunca aparecia, e o `root.wait_window(dialog)` seguinte
bloqueava para sempre numa janela que ninguem podia ver, focar ou fechar.
O `withdraw()` nao e o defeito e nao foi mexido: ele existe porque a pergunta de
idioma tem de ser feita ANTES de `load_settings()`, e a janela principal nao
pode piscar antes disso. O `transient` e que passa a ser aplicado so quando o
master esta visivel -- o caso de **Configuracoes -> Idioma**, onde ele vale
(empilhamento correto sobre a janela principal, sem segundo botao na barra de
tarefas). Um `TclError` do root degrada para "sem transient": perder o
comportamento transient e cosmetico, perder o dialogo para o app.
Isto so acontece com `config.local.yaml` ausente, ou seja **so numa maquina
limpa** -- precisamente a do operador, e nunca a de quem desenvolve.
Escapou dos testes porque todos os 9 casos de `test_language_dialog.py` recebem
um Toplevel visivel da fixture `tkinter_root`; nenhum reproduzia o root
withdrawn do arranque real. Os tres testes novos reproduzem, e o primeiro falha
contra o codigo antigo com a mensagem exata do defeito:
the chooser was never mapped: state='withdrawn' size=(1, 1);
wait_window would block on it forever
Conferido tambem que o splash NAO tem o mesmo problema: ele usa
`overrideredirect(True)`, que nao herda o estado do master.
GUI: 1209 passed. ruff e mypy limpos.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Teste de instalação do zero em
C:\DRerio\DRerio-LogAIdepois do #561: oinstall.batcorreu inteiro, mas o duplo-clique no ícone não fazia absolutamente nada. Pelo terminal, o app parava aqui e nunca mais saía:O atalho estava correto. O app é que travava — e só numa máquina limpa.
O defeito
Depois daquela linha vêm
Tk(),root.withdraw()e o seletor de idioma de primeira execução. Ele chamavadialog.transient(root).Uma janela transient herda o estado mapeado do master. Com o master withdrawn ela também fica withdrawn — e nem
deiconify()nemlift()revertem. Medido no Windows:O chooser era criado, nunca aparecia, e o
root.wait_window(dialog)seguinte bloqueava para sempre numa janela que ninguém podia ver, focar ou fechar.O
withdraw()não é o defeito e não foi mexido. Ele existe porque a pergunta de idioma tem de ser feita antes deload_settings()— é ela que escreveui.language— e a janela principal não pode piscar antes disso. O que muda é otransient, agora aplicado só quando o master está visível: o caso de Configurações → Idioma, onde ele vale (empilhamento correto sobre a janela principal, sem segundo botão na barra de tarefas). UmTclErrordo root degrada para "sem transient" — perder o comportamento transient é cosmético, perder o diálogo para o app.Só acontece com
config.local.yamlausente, ou seja só numa máquina limpa — precisamente a do operador, e nunca a de quem desenvolve. Foi por isso que passou despercebido até o teste de instalação real.Conferido que o splash não tem o mesmo problema: ele usa
overrideredirect(True), que não herda o estado do master.Por que os testes não pegaram
Todos os 9 casos de
tests/ui/test_language_dialog.pyrecebem um Toplevel visível da fixturetkinter_root. Nenhum reproduzia o root withdrawn do arranque real. Os três testes novos reproduzem, e o primeiro falha contra o código antigo com a mensagem exata do defeito:Segundo commit: nada mais pode falhar em silêncio
Enquanto diagnosticava isto, ficou claro que o problema real não era o travamento em si — era não haver como saber qual era.
run_appconverte falhas em diálogo, mas só a partir do ponto em que a raiz Tk existe. O parsing de argumentos, o_setup_logginge o próprioTk()rodam antes disso, e uma falha do próprio handler de erro também escapa. Nesses casos o traceback vai parasys.stderr— e sobpythonw, escolhido no #561 justamente para não deixar um console atrás da janela,sys.stderréNone. O traceback vai para lugar nenhum e o processo morre calado.Para quem tem terminal isso é um contratempo. Para o operador que só tem o ícone, é o pior desfecho possível.
__main__.main()agora envolverun_appe, quando não há stderr, mostra o traceback num message box junto com o caminho delogs/analysis.loge o comando que reproduz o erro. Com console é no-op deliberado: ali o traceback do próprio Python é melhor que um modal, e a suíte afirma sobre ele.SystemExitpassa direto — o caminho de pesos ausentes já mostra um diálogo que nomeia a pasta e o remédio, e um segundo modal genérico só pioraria.Validado sob
pythonwde verdade, iniciado destacado como o Explorer faz (stderr=Noneconfirmado no processo).Verificação
mainatual: 6737 passed, 4 skipped-m gui -n0): 1209 passedrufflimpo;mypylimpo em 903 arquivos;pre-commit --all-filesverde🤖 Generated with Claude Code