Skip to content

Roadmap P0+P1: corrige bugs críticos e cria a fundação (81 testes) - #4

Merged
afernandes merged 6 commits into
mainfrom
fix/roadmap-p0-p1
Jul 28, 2026
Merged

Roadmap P0+P1: corrige bugs críticos e cria a fundação (81 testes)#4
afernandes merged 6 commits into
mainfrom
fix/roadmap-p0-p1

Conversation

@afernandes

Copy link
Copy Markdown
Owner

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

SettingsService e LogGroupService gravavam na pasta do executável (Directory.GetCurrentDirectory()). Numa instalação MSIX isso é C:\Program Files\WindowsApps\..., somente leitura: a gravação falhava, o catch { } engolia o erro e o usuário perdia grupos e configurações sem nenhum aviso.

Novo AppStorage resolve a pasta via LocalApplicationData — gravável nos dois casos (no app empacotado o Windows redireciona para o armazenamento privado do pacote). Inclui:

  • migração dos arquivos que já existam ao lado do exe (copia, não move — uma versão antiga instalada pode ainda depender deles);
  • escrita atômica (temporário + troca), para uma falha no meio da gravação não deixar JSON truncado;
  • quarentena: JSON inválido vai para .corrupt. Antes, um groups.json corrompido 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 ramo Fatal, justamente onde e.Level lançaria. Corrigido e coberto por teste de regressão.

3. Ciclo de vida do LogViewer

  • AplicarFiltros era async void: exceção escaparia para o contexto de sincronização e derrubaria a UI. Virou async Task com wrapper síncrono para os setters.
  • O CancellationTokenSource nunca era descartado. Agora cada invocação é dona do próprio CTS e o descarta no finally; o anterior é apenas cancelado (a invocação dele ainda pode estar lendo o token).
  • O componente não implementava IDisposable e os handlers de Store.Changed/GroupService.Changed nunca 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:

Suíte Cobre
LogFilterTests filtro rápido (Erros inclui Fatal), arquivo, datas inclusivas por dia, busca, ordenação, composição
AppStorageTests roundtrip, escrita atômica, migração do lado do exe, quarentena
PersistenceTests grupos/configurações sobrevivem a reabertura, migração, JSON inválido não apaga dados
LogStoreTests parsing CLEF e .clef.gz reais, .gz listado mas não carregado, variáveis de ambiente, padrões ignorados, (des)marcação, isolamento do Snapshot
HelpersTests TextFormatter (unescape, unicode, JSON embutido) e StackTraceHighlighter (classes, escape de HTML — o resultado vai como MarkupString)

ci.yml passa a rodar dotnet test como gate.

5. Erros visíveis

Novo AppLog (log próprio em %LOCALAPPDATA%\ClefExplorer\logs\, expurgo de 7 dias) + NotificationService da Omni na UI. O LogStore passa a expor LoadFailures e o LogViewer reporta: 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. LogStore encapsulado

Events era 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). Removidos Filtered(), a propriedade Filter e o LoadFromFolder síncrono Deprecated. A filtragem virou a classe pura LogFilter.

Efeito colateral positivo

Os avisos CS* do build zeraram (6 → 0; restou apenas um MSB3277 vindo do próprio pacote WebView2). Junto vieram correções do item 23: dispose do BlazorWebView na thread de UI (era Task.Run fire-and-forget — o acesso cross-thread que o WinForms não suporta) e Environment.ProcessPath sem desreferência nula.

Verificação

  • dotnet build — 0 erros, 0 avisos CS*.
  • dotnet test81/81 verdes (Debug e Release).
  • Smoke test no app real: sobe e renderiza; abrindo um .clef inválido, a falha aparece no log de diagnóstico ([WRN] Falha ao ler '...corrompido.clef') e dispara a notificação.
  • Isolamento dos testes verificado: a suíte não escreve no %LOCALAPPDATA% real (o AppLog é redirecionado por um ModuleInitializer).

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.
  • A migração copia em vez de mover, então quem tiver a versão antiga instalada continua funcionando.
  • O projeto de testes acompanha o TFM Windows do app (necessário para referenciá-lo); o .wapproj é ignorado no dotnet restore com NU1503 — quem o constrói é o MSBuild no release.yml.
  • Restam os itens P2–P4 do roadmap (features e evolução).

🤖 Generated with Claude Code

afernandes and others added 6 commits July 28, 2026 01:08
…/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>
Copilot AI review requested due to automatic review settings July 28, 2026 04:35
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 + AppLog e pluga SettingsService/LogGroupService para persistência segura (migração, escrita atômica, quarentena e erros reportáveis).
  • Encapsula o LogStore (snapshot/contagem + falhas de carga) e extrai filtragem para LogFilter com correções de precedência e ciclo de vida no LogViewer.
  • Adiciona projeto de testes xUnit (81 testes) e executa dotnet test no 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.

Comment thread src/Services/LogStore.cs
Comment thread src/Services/LogStore.cs
@afernandes
afernandes merged commit b2ec992 into main Jul 28, 2026
2 checks passed
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.
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.

2 participants