Roadmap P0+P1: corrige bugs críticos e cria a fundação (81 testes) - #4
Merged
Conversation
…/P0.3) Filtro "Erros" (precedencia de operador): "e != null && Error || Fatal" era lido como "(e != null && Error) || Fatal", entao o teste de nulo nao protegia o ramo Fatal — justamente onde e.Level lancaria. Como os eventos nunca sao nulos (vem de MapLogEvent) e os demais filtros tambem nao checam nulo, a checagem foi removida e a intencao ficou explicita: Error OU Fatal. Some tambem o aviso CS8602 (6 -> 5 avisos). Ciclo de vida do LogViewer: - AplicarFiltros deixa de ser "async void" (cuja excecao escaparia para o contexto de sincronizacao e derrubaria a UI) e vira AplicarFiltrosAsync, com um wrapper sincrono para os setters de propriedade. - O CancellationTokenSource passa a ser descartado: cada invocacao e dona do proprio CTS e o descarta no finally; cancelamos o anterior sem descarta-lo, pois a invocacao dele ainda pode estar lendo o token. - O componente implementa IDisposable e desinscreve Store.Changed e GroupService.Changed. Como os servicos sao singletons, handlers nao removidos mantinham o componente vivo. Os lambdas viraram metodos nomeados para permitir a desinscricao. Adiciona LogStore.Count (lido sob lock) para substituir Store.Events.Any(), que enumerava a lista viva — o encapsulamento completo vem no proximo passo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SettingsService e LogGroupService gravavam settings.json/groups.json na pasta do executavel (Directory.GetCurrentDirectory, apontada por FixCurrentPath). Na instalacao da Microsoft Store isso e C:\Program Files\WindowsApps\..., que e somente leitura: a gravacao falhava, o catch engolia o erro e o usuario perdia grupos e configuracoes sem nenhum aviso. Adiciona AppStorage, que resolve a pasta de dados via LocalApplicationData — gravavel nos dois casos (instalacao normal usa %LOCALAPPDATA%; no app empacotado o Windows redireciona para o armazenamento privado do pacote) — e centraliza leitura/escrita: - Migra na primeira execucao os arquivos que ja existam ao lado do exe (copia, nao move: uma versao antiga instalada pode ainda depender deles). - Escrita atomica (arquivo temporario + troca), para que uma falha no meio da gravacao nao deixe um JSON truncado no lugar do arquivo bom. - JSON invalido vai para <nome>.corrupt em vez de ser sobrescrito. Antes, um groups.json invalido zerava a lista em memoria e a gravacao seguinte apagava os grupos do usuario. Os dois servicos passam a expor LastError; a exibicao ao usuario vem no passo de tratamento de erros (roadmap P1.5). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
LogStore.Events expunha a lista viva de eventos — foi exatamente isso que causou o crash 'More than one sibling has the same key value' (enumerar a List durante mutacao em outra thread). A correcao anterior adicionou Snapshot(), mas a propriedade continuava publica e ainda era usada. Agora Events e privado: quem precisa de contagem usa Count, quem precisa iterar usa Snapshot(), ambos sob lock. Remove codigo morto: Filtered(), a propriedade Filter (e o campo _filter) e o LoadFromFolder sincrono marcado 'Deprecated', que usava GetAwaiter().GetResult() e ninguem chamava. Extrai a filtragem do LogViewer para LogFilter + LogFilterCriteria. A logica estava embutida no componente Razor, onde so era exercitada pela UI; como classe pura fica testavel isoladamente (proximo passo do roadmap). O comportamento e preservado, incluindo a ordenacao decrescente por timestamp. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A solucao tinha apenas o app e o projeto de empacotamento — zero testes — e o CI so compilava. Os bugs corrigidos durante a migracao (race de chave duplicada, selecao da arvore desmarcando irmaos, no "arquivo E pasta") e os corrigidos agora passariam despercebidos por qualquer refatoracao. Cria test/ClefExplorer.Tests (xUnit) com 77 testes cobrindo a logica que concentra o risco: - LogFilterTests: filtro rapido (Erros inclui Fatal — trava a regressao de precedencia), arquivo, intervalo de datas inclusivo por dia, busca textual em mensagem/excecao, ordenacao decrescente e composicao dos criterios. - AppStorageTests: roundtrip, criacao da pasta, escrita atomica sem deixar .tmp, migracao da pasta do executavel (copia, nao move; arquivo existente vence o legado) e quarentena de arquivo invalido. - PersistenceTests: grupos e configuracoes sobrevivem a reabertura, sao migrados do lado do exe, e um JSON invalido vai para .corrupt em vez de apagar os dados do usuario; pasta nao gravavel reporta LastError. - LogStoreTests: parsing CLEF e .clef.gz contra arquivos reais em pasta temporaria, nivel default Information, ordenacao, varredura recursiva, .gz listado mas nao carregado por padrao, expansao de variaveis de ambiente, padroes de arquivo/linha ignorados, arquivo explicito vence o padrao de exclusao, (des)marcacao de arquivos e isolamento do Snapshot. - HelpersTests: TextFormatter (unescape, unicode, indentacao de JSON embutido, acentos sem escape) e StackTraceHighlighter (classes clef-st-*, nome do arquivo com caminho no title, frames de framework, e o escape de HTML — o resultado vai como MarkupString). O projeto de testes acompanha o TFM Windows do app (necessario para referencia-lo). ci.yml passa a restaurar a solucao e rodar dotnet test como gate; o .wapproj e ignorado no restore (NU1503), pois quem o constroi e o MSBuild no release.yml. Inclui tambem o ROADMAP.md com o levantamento de pendencias que originou estes trabalhos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Havia 15 blocos catch que engoliam excecoes em silencio: o usuario nunca sabia
que um arquivo nao pode ser lido, que o disco estava cheio ou que o JSON de
configuracao estava corrompido — simplesmente "nao aparecia nada".
AppLog: log de diagnostico do proprio app em
%LOCALAPPDATA%\ClefExplorer\logs\clefexplorer-AAAA-MM-DD.log, com expurgo dos
arquivos com mais de 7 dias. E estatico porque precisa funcionar onde nao ha
injecao de dependencia (construtores de servico, handlers do WinForms) e nunca
lanca — falhar ao registrar um erro nao pode derrubar o app. Expoe RedirectTo
para os testes nao gravarem no %LOCALAPPDATA% real (chamado num
ModuleInitializer da suite).
LogStore: as falhas de leitura deixam de ser descartadas e viram LoadFailures
(caminho + motivo), limpas a cada carregamento. Um caminho inexistente tambem
passa a contar como falha, em vez de sumir silenciosamente.
LogViewer: injeta NotificationService (o OmniNotificationHost ja vinha no
OmniOverlayHosts) e reporta:
- erros de carga das configuracoes/grupos, que acontecem na construcao via DI,
antes de existir UI — por isso so podem ser mostrados aqui;
- resumo das falhas de leitura ("N arquivos nao puderam ser lidos"), detalhando
ate 3 e remetendo ao log para o restante;
- falha ao aplicar filtros.
MainForm: o BlazorWebView passa a ser descartado na thread de UI — descarta-lo
num Task.Run fire-and-forget era justamente o acesso cross-thread que o
WinForms nao suporta. O caminho do index.html passa a usar
Environment.ProcessPath (correto no publish single-file) sem desreferencia
nula. Com isso os avisos CS do build zeram (restou apenas o MSB3277 vindo do
proprio pacote WebView2).
Adiciona 4 testes cobrindo o registro e a limpeza das falhas de leitura.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
There was a problem hiding this comment.
Pull request overview
Este PR implementa os itens P0 (correções críticas) e P1 (fundação) do roadmap: torna persistência compatível com MSIX/Store, torna falhas visíveis/diagnosticáveis, encapsula o LogStore para evitar races/crashes, e adiciona uma suíte inicial de testes com gate no CI.
Changes:
- Introduz
AppStorage+AppLoge plugaSettingsService/LogGroupServicepara persistência segura (migração, escrita atômica, quarentena e erros reportáveis). - Encapsula o
LogStore(snapshot/contagem + falhas de carga) e extrai filtragem paraLogFiltercom correções de precedência e ciclo de vida noLogViewer. - Adiciona projeto de testes xUnit (81 testes) e executa
dotnet testno workflow de CI.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/ClefExplorer.Tests/TestSetup.cs | Redireciona AppLog para pasta temporária durante testes. |
| test/ClefExplorer.Tests/PersistenceTests.cs | Testa persistência/migração/quarentena para settings e grupos. |
| test/ClefExplorer.Tests/LogStoreTests.cs | Testa parsing/carga de logs, .gz, exclusões, seleção e falhas reportadas. |
| test/ClefExplorer.Tests/LogFilterTests.cs | Testa contrato da filtragem (inclui regressão Error+Fatal). |
| test/ClefExplorer.Tests/HelpersTests.cs | Testa TextFormatter e StackTraceHighlighter (inclui escape de HTML). |
| test/ClefExplorer.Tests/ClefExplorer.Tests.csproj | Define projeto de testes (TFM Windows) e dependências. |
| test/ClefExplorer.Tests/AppStorageTests.cs | Testa AppStorage (roundtrip, migração, escrita atômica, quarentena). |
| src/Services/SettingsService.cs | Migra para AppStorage, registra LastError e loga falhas via AppLog. |
| src/Services/LogStore.cs | Encapsula eventos (count/snapshot), adiciona LoadFailures, melhora erros. |
| src/Services/LogGroupService.cs | Migra para AppStorage, registra LastError e loga/quarentena falhas. |
| src/Services/LogFilter.cs | Extrai filtragem para classe pura e testável. |
| src/Services/AppStorage.cs | Implementa storage em %LOCALAPPDATA% com migração/quarentena/escrita atômica. |
| src/Services/AppLog.cs | Cria log diagnóstico do app com expurgo e redirecionamento para testes. |
| src/Program.cs | Registra AppStorage no DI. |
| src/Models/LoadFailure.cs | Modelo para reportar falhas de carga ao usuário. |
| src/MainForm.cs | Remove catch silencioso e ajusta caminhos/dispose com log diagnóstico. |
| src/Components/LogViewer.razor | Corrige lifecycle (IDisposable/CTS/async), aplica LogFilter e notifica erros. |
| ROADMAP.md | Adiciona roadmap detalhado e marca P0/P1 como concluídos. |
| ClefExplorer.sln | Inclui projeto de testes na solução. |
| .github/workflows/ci.yml | Executa restore/build/test e mantém publish smoke. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
afernandes
added a commit
that referenced
this pull request
Jul 28, 2026
A main recebeu o PR #4 (P0+P1). Incorpora para o PR do P3 sair limpo.
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.
Resumo
Executa os itens P0 (crítico) e P1 (fundação) do ROADMAP.md, incluído aqui: três bugs que afetam usuários hoje e a base que faltava para mexer no código com segurança.
O resultado principal: 81 testes automatizados onde antes havia zero, rodando como gate no CI.
P0 — Bugs críticos
1. Configurações e grupos não persistiam na versão da Store
SettingsServiceeLogGroupServicegravavam na pasta do executável (Directory.GetCurrentDirectory()). Numa instalação MSIX isso éC:\Program Files\WindowsApps\..., somente leitura: a gravação falhava, ocatch { }engolia o erro e o usuário perdia grupos e configurações sem nenhum aviso.Novo
AppStorageresolve a pasta viaLocalApplicationData— gravável nos dois casos (no app empacotado o Windows redireciona para o armazenamento privado do pacote). Inclui:.corrupt. Antes, umgroups.jsoncorrompido zerava a lista em memória e a gravação seguinte apagava os grupos do usuário.2. Filtro "Erros" e a precedência de operador
e != null && Error || Fatalé lido como(e != null && Error) || Fatal— o teste de nulo não protegia o ramoFatal, justamente ondee.Levellançaria. Corrigido e coberto por teste de regressão.3. Ciclo de vida do
LogViewerAplicarFiltroseraasync void: exceção escaparia para o contexto de sincronização e derrubaria a UI. Virouasync Taskcom wrapper síncrono para os setters.CancellationTokenSourcenunca era descartado. Agora cada invocação é dona do próprio CTS e o descarta nofinally; o anterior é apenas cancelado (a invocação dele ainda pode estar lendo o token).IDisposablee os handlers deStore.Changed/GroupService.Changednunca eram desinscritos — como os serviços são singletons, isso mantinha o componente vivo.P1 — Fundação
4. Projeto de testes + gate no CI
test/ClefExplorer.Tests(xUnit) com 81 testes:LogFilterTestsAppStorageTestsPersistenceTestsLogStoreTests.clef.gzreais,.gzlistado mas não carregado, variáveis de ambiente, padrões ignorados, (des)marcação, isolamento doSnapshotHelpersTestsTextFormatter(unescape, unicode, JSON embutido) eStackTraceHighlighter(classes, escape de HTML — o resultado vai comoMarkupString)ci.ymlpassa a rodardotnet testcomo gate.5. Erros visíveis
Novo
AppLog(log próprio em%LOCALAPPDATA%\ClefExplorer\logs\, expurgo de 7 dias) +NotificationServiceda Omni na UI. OLogStorepassa a exporLoadFailurese oLogViewerreporta: erros de carga de configuração/grupos (que ocorrem na DI, antes de existir UI), resumo de arquivos ilegíveis e falha ao filtrar.6.
LogStoreencapsuladoEventsera público e expunha a lista viva — a origem do crash "More than one sibling has the same key value". Agora é privado (Count/Snapshot()sob lock). RemovidosFiltered(), a propriedadeFiltere oLoadFromFoldersíncronoDeprecated. A filtragem virou a classe puraLogFilter.Efeito colateral positivo
Os avisos
CS*do build zeraram (6 → 0; restou apenas umMSB3277vindo do próprio pacote WebView2). Junto vieram correções do item 23: dispose doBlazorWebViewna thread de UI (eraTask.Runfire-and-forget — o acesso cross-thread que o WinForms não suporta) eEnvironment.ProcessPathsem desreferência nula.Verificação
dotnet build— 0 erros, 0 avisosCS*.dotnet test— 81/81 verdes (Debug e Release)..clefinválido, a falha aparece no log de diagnóstico ([WRN] Falha ao ler '...corrompido.clef') e dispara a notificação.%LOCALAPPDATA%real (oAppLogé redirecionado por umModuleInitializer).Notas para o revisor
AppLogé estático de propósito: precisa funcionar onde não há DI (construtores de serviço, handlers do WinForms). Nunca lança..wapprojé ignorado nodotnet restorecomNU1503— quem o constrói é o MSBuild norelease.yml.🤖 Generated with Claude Code