Skip to content

Roadmap P4.19: painel de estatísticas (+ recolhe P7/P8 que ficaram fora da main) - #9

Merged
afernandes merged 11 commits into
mainfrom
feat/roadmap-p4
Jul 29, 2026
Merged

Roadmap P4.19: painel de estatísticas (+ recolhe P7/P8 que ficaram fora da main)#9
afernandes merged 11 commits into
mainfrom
feat/roadmap-p4

Conversation

@afernandes

Copy link
Copy Markdown
Owner

Resumo

Implementa o item 19 — painel de estatísticas do ROADMAP.md, o último item grande em aberto. Com ele, o P4 fica concluído exceto pelo i18n (item 22), que depende de uma decisão de produto.

⚠️ Este PR aponta para main e recolhe o resto da pilha. Os PRs #7 e #8 foram mergeados em branches de feature, não na main — então o painel de detalhes, a visão em tabela e os itens 18/20/21/23 ainda não estavam na main. Este PR os traz junto com as estatísticas.

O painel

Terceiro modo de visualização, ao lado de lista e tabela. Mostra uma visão agregada do conjunto filtrado — não do arquivo inteiro — então os números respondem ao que está em tela.

  • Resumo: total, erros e fatais, taxa de erro, e barras por nível.
  • Timeline: volume por fatia de tempo com a série de erros sobreposta — é o que revela os picos.
  • Rankings: origens, mensagens e exceções mais frequentes.

Duas decisões de agregação que fazem a diferença

Mensagens são agrupadas pelo MessageTemplate, não pelo texto renderizado. Sem isso, 600 execuções de "Pedido {Id} processado" viram 600 linhas de contagem 1 e o ranking não mostra nada. Verificado no app: as 600 colapsaram em 1 entrada.

Exceções são agrupadas pela primeira linha (tipo + mensagem). O stack trace inteiro é único por ocorrência e também não agruparia nada.

Os níveis saem em ordem de gravidade, não por contagem — a lista fica estável entre filtragens, então o olho encontra "Error" sempre no mesmo lugar.

Tudo é clicável

Clicar num nível, origem, mensagem ou exceção aplica o filtro correspondente. É o que torna o painel um ponto de partida da investigação em vez de um relatório.

Verificado no app: clicando em ERROR, o conjunto caiu de 600 para 61 eventos, o resumo recalculou para 100% de taxa de erro, e a timeline se re-fatiou de 31,5 min para 3 min — ajustando automaticamente ao novo período.

Verificação

  • 191 testes verdes (18 novos), 0 erros, 0 avisos CS*.
  • Validado no app com 600 eventos contendo um pico de erros plantado: o pico aparece claramente na timeline.
  • 18 testes cobrindo: ordem por gravidade, nível desconhecido, agrupamento por template e por primeira linha, SourceContext com fallback para o arquivo, número de fatias, separação de erros, o evento do limite superior caindo na última fatia, tudo no mesmo instante, fatia encolhendo com o período e conjunto vazio.

Notas para o revisor

  • O cálculo só roda no modo que o usa — na lista e na tabela seria trabalho jogado fora.
  • O LogStatistics é puro (sem dependência de UI), por isso todo o comportamento é testável direto.
  • Sobra do P4: item 22 (i18n). O roadmap registra que "só vale a pena se houver intenção real de alcance internacional na Store" — é decisão sua, não técnica.

🤖 Generated with Claude Code

afernandes and others added 10 commits July 28, 2026 11:42
O detalhe so abria a direita. Um botao na toolbar passa a alternar entre a
direita e abaixo: a direita funciona melhor em telas largas, abaixo e melhor
para stack traces longos e telas estreitas.

O icone aponta para ONDE o painel vai (seta para baixo / para a direita), e nao
onde ele esta — a lib nao tem icones de painel, e a seta remove a ambiguidade.
O botao so aparece quando ha um detalhe aberto para reposicionar.

O splitter recebe @key na orientacao: ele mede e fixa os tamanhos dos panes na
inicializacao, entao reaproveitar a instancia deixaria as larguras da
disposicao anterior aplicadas na nova.

A preferencia e persistida em ui.json — e nao no settings.json de proposito: o
SettingsService dispara Changed ao salvar e o LogStore reage recarregando todos
os arquivos, o que seria absurdo para uma preferencia visual. O enum e gravado
por nome (Bottom), ja que o arquivo e editavel a mao.

Validado no app: alterna nos dois sentidos e a escolha sobrevive a reabertura.
6 testes novos, incluindo arquivo corrompido e valor desconhecido caindo no
padrao.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Alem da lista, um botao na toolbar alterna para uma tabela com ordenacao,
agrupamento (arrastando a coluna para a faixa acima), filtro por coluna e
redimensionamento. A escolha e persistida.

Colunas derivadas do CONTEUDO: logs CLEF carregam propriedades estruturadas que
variam conforme a aplicacao que os gerou, entao fixa-las na mao nao
funcionaria. O LogColumnDiscovery amostra os eventos carregados, conta a
frequencia de cada propriedade e oferece como coluna as que aparecem o
suficiente, com o nome ja legivel - "MachineName" vira "Machine Name". O
desempate por nome mantem a ordem estavel entre aberturas.

O seletor de colunas e proprio, e nao o embutido (AllowColumnVisibility=false).
Motivo: a coluna aplica a visibilidade no proprio OnInitialized, entao toda vez
que o grid e recriado - o que acontece ao abrir/fechar o detalhe, ja que o
layout troca de splitter - as escolhas do menu interno se perdiam. Mantendo o
conjunto do lado do app, ele sobrevive a recriacao e ainda persiste entre
execucoes.

Tentei estabilizar a arvore mantendo o splitter sempre montado com o pane do
detalhe colapsado, mas isso reintroduz a race de medicao do splitter aninhado
(o conteudo mede 0px e some). Por isso a renderizacao condicional foi mantida e
o problema foi resolvido pelo lado do estado.

19 testes novos: descoberta (frequencia, corte de raras, exclusao das que ja tem
coluna fixa, teto, estabilidade, amostragem), rotulo legivel e valor da celula
(sem as aspas que o ScalarValue adiciona).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Com 'pull_request: branches: [main]', PRs empilhados — que apontam para outra
branch de feature — ficavam sem CI: aconteceu no #5 e no #7, onde o gate de
testes so passou a valer depois de disparar o workflow na mao. Removido o
filtro de base; o gatilho de push continua restrito a main.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A versao vivia solta no csproj e ficou em 1.0.0 enquanto a Store ja estava em
1.0.2 — o titulo da janela, que le a versao do assembly, mostrava um numero
diferente do publicado.

Directory.Build.props passa a ser a fonte unica:
- o csproj herda Version/FileVersion/AssemblyVersion (verificado: o assembly
  agora sai 1.1.0.0);
- publish-store-package.ps1 usa esse valor quando -Version nao e informado;
- release.yml passa /p:Version com a versao da tag, entao o exe dentro do
  pacote carrega a mesma versao carimbada no manifesto — antes o manifesto ia
  com a versao da tag e o assembly ficava em 1.0.0.

Higiene:
- o tipo de arquivo registrado virou 'Clef Log File'; 'Reader Log File' era
  sobra de copy/paste e divergia do rotulo do Package.appxmanifest;
- RuntimeIdentifiers fica so em win-x64: e o que o CI publica, o que o pacote da
  Store contem e o que o README anuncia. Declarar win-x86 sugeria um suporte
  que nao existe.

Nao suprimi o aviso MSB3277 remanescente: ele vem da unificacao de WindowsBase
pelo proprio pacote WebView2 e mascarar conflitos de assembly com NoWarn e pior
do que conviver com o ruido. Os avisos CS do nosso codigo seguem zerados.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A lista montava um no no DOM por evento da pagina. O roadmap pedia para medir
antes: com paginas de ate 100 a virtualizacao renderia pouco, entao o ganho de
verdade vinha junto com paginas maiores.

A lista passa a usar OmniVirtualize (Height=null: o pai ja limita a altura e faz
o scroll; sem altura limitada o Virtualize renderiza tudo) e o seletor ganha
250/500/1000 itens por pagina. Verificado com 5000 eventos: pagina de 1000 rola
sem travar.

Correcao encontrada durante esse teste: OmniPagination e zero-based e o app
conta a partir de 1 — que e o numero mostrado no "X-Y de Z". Sem conversao, a
pagina 1 aparecia como "2 / 100" e a ultima pagina ficava inalcancavel, porque o
clamp do app ia ate UltimaPagina enquanto o componente esperava
UltimaPagina - 1. A traducao ficou na fronteira, dentro da LogToolbar. Bug meu,
introduzido na migracao do P2 ao trocar os botoes manuais pelo componente.
Verificado: "1-50 de 5000" com "1 / 100", e o proximo leva a "51-100" / "2 / 100".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nao houve passe de a11y depois da migracao. Os componentes da Omni ja vem
tratados, mas os elementos clef-* proprios nao.

Lista de eventos: era uma pilha de div com @OnClick - nao recebia foco, nao
respondia ao teclado e o leitor de tela nao a anunciava como lista selecionavel.
Agora e role=listbox com itens role=option, tabindex e aria-selected, ativaveis
por Enter/Espaco. Cada item ganhou um aria-label montado com nivel, hora,
mensagem e a marca de excecao: sem ele o item seria lido como textos soltos e o
nivel, que visualmente e um badge colorido, se perderia.

Itens de grupo viraram button de verdade em vez de div com @OnClick - foco,
teclado e semantica de graca. Nao ha nada interativo aninhado, entao a troca e
segura; o CSS ganhou o reset de aparencia correspondente.

Indicador de foco: nenhum elemento clef-* tinha. Adicionado via :focus-visible,
que mostra o anel so na navegacao por teclado, sem poluir o clique de mouse.

O contraste ja tinha sido tratado na migracao (WCAG AA medido no DOM nos dois
temas), entao ficou de fora deste passe.

Nao consegui confirmar o anel de foco na tela: a maquina bloqueou durante o
teste. As mudancas sao estruturais e compilam, mas a verificacao visual fica
pendente.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Roadmap P4: virtualização, acessibilidade, versionamento e higiene (+ bug de paginação)
Terceiro modo de visualizacao, ao lado de lista e tabela: uma visao agregada do
conjunto FILTRADO — nao do arquivo inteiro. Assim os numeros respondem ao que
esta em tela: filtrou por periodo, as estatisticas acompanham.

O painel traz total, erros e taxa de erro; contagem por nivel; timeline com o
volume e, sobreposta, a serie de erros (e o que revela os picos); e rankings de
origens, mensagens e excecoes.

Duas decisoes de agregacao que fazem a diferenca:

- Mensagens sao agrupadas pelo TEMPLATE, nao pelo texto renderizado. Sem isso,
  600 execucoes de "Pedido {Id} processado" viram 600 linhas de contagem 1 e o
  ranking nao mostra nada. Verificado no app: as 600 colapsaram em 1 entrada.
- Excecoes sao agrupadas pela primeira linha (tipo + mensagem). O stack trace
  inteiro e unico por ocorrencia e tambem nao agruparia nada.

Os niveis saem em ordem de gravidade, e nao por contagem: a lista fica estavel
entre filtragens, entao o olho encontra "Error" sempre no mesmo lugar.

Tudo e clicavel e aplica o filtro correspondente — e o que torna o painel um
ponto de partida da investigacao, e nao um relatorio. A fatia da timeline se
ajusta ao periodo coberto; verificado no app: ao clicar em ERROR, o conjunto caiu
de 600 para 61 eventos e a timeline re-fatiou de 31,5 para 3 minutos.

O calculo so roda no modo que o usa, para nao gastar trabalho a toa na lista e na
tabela.

18 testes: ordem por gravidade, nivel desconhecido, agrupamento por template e
por primeira linha da excecao, SourceContext com fallback para o arquivo, numero
de fatias, separacao de erros na timeline, o evento do limite superior caindo na
ultima fatia, tudo no mesmo instante, fatia encolhendo com o periodo e conjunto
vazio.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 29, 2026 00:40
@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 conclui o item 19 do roadmap adicionando um terceiro modo de visualização (“Stats”) com agregações (resumo, timeline e rankings) sobre o conjunto filtrado, além de incorporar mudanças de P7/P8 (visão em tabela, preferências de UI, acessibilidade, versionamento e ajustes de CI).

Changes:

  • Adiciona o painel de estatísticas (agregações puras em LogStatistics + UI LogStatsPanel) e integra como novo LogViewMode.
  • Introduz visão em tabela com descoberta dinâmica de colunas (grid + seletor/persistência de colunas).
  • Move preferências visuais para ui.json, melhora acessibilidade/teclado/foco e unifica versionamento via Directory.Build.props + ajustes em scripts/workflows.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
test/ClefExplorer.Tests/UiPreferencesTests.cs Testes de persistência/robustez do ui.json e preferências de layout.
test/ClefExplorer.Tests/LogStatisticsTests.cs Testes das agregações do painel de estatísticas (níveis, mensagens, exceções, timeline).
test/ClefExplorer.Tests/LogColumnDiscoveryTests.cs Testes de descoberta/humanização/format de colunas derivadas de propriedades.
src/wwwroot/css/app.css Estilos para grid, foco visível e painel de estatísticas.
src/Services/UiPreferencesService.cs Serviço para persistir preferências de UI em ui.json.
src/Services/FileAssociationService.cs Ajusta rótulo do tipo de arquivo registrado no registry.
src/Program.cs Registra UiPreferencesService no DI.
src/Models/UiEnums.cs Adiciona enums de UI (LogViewMode, DetailPanelPosition).
src/Helpers/LogStatistics.cs Implementa agregações e timeline do conjunto filtrado.
src/Helpers/LogGridColumns.cs Define colunas fixas e defaults do modo tabela.
src/Helpers/LogColumnDiscovery.cs Descobre colunas por frequência e formata valores/humaniza nomes.
src/Components/LogViewer.razor Integra os 3 modos (lista/tabela/stats), recalcula colunas/stats conforme filtro.
src/Components/LogToolbar.razor UI para alternar modo de visualização + ajuste de paginação zero-based.
src/Components/LogStatsPanel.razor UI do painel de estatísticas com cards, rankings e chart.
src/Components/LogList.razor Virtualização + ARIA/teclado para lista de eventos.
src/Components/LogHeader.razor Amplia opções de tamanho de página (até 1000) após virtualização.
src/Components/LogGroupManager.razor Troca item clicável de grupo para <button> com semântica/teclado.
src/Components/LogGrid.razor Nova visão em tabela (OmniDataGrid) + seletor de colunas customizado.
src/ClefExplorer.csproj Ajusta RIDs (só win-x64) e remove versões locais (vem do props).
ROADMAP.md Marca itens 18–23 como concluídos/parciais conforme implementado.
publish-store-package.ps1 Permite herdar versão do Directory.Build.props quando omitida.
Directory.Build.props Fonte única da versão (ClefVersion/Version/FileVersion/AssemblyVersion).
.github/workflows/release.yml Carimba manifesto e passa versão para build do pacote da Store.
.github/workflows/ci.yml Faz CI rodar para PRs sem restringir base branch.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/workflows/release.yml
Comment thread src/Helpers/LogStatistics.cs
Comment thread publish-store-package.ps1 Outdated
Comment thread publish-store-package.ps1
Comment thread src/Components/LogList.razor Outdated
Versao do assembly nao acompanhava o override (2 apontamentos): FileVersion e
AssemblyVersion derivavam de $(ClefVersion), entao um /p:Version=2.0.0 mudava so
o pacote e o assembly continuava na versao do arquivo — exatamente a divergencia
que a fonte unica existe para evitar. Passam a derivar de $(Version). Provado:
build normal sai 1.1.0.0; com /p:Version=2.5.0 sai 2.5.0.0.

Junto: o publish-store-package.ps1 carimbava o manifesto mas nao passava versao
alguma para o MSBuild, entao o exe dentro do pacote ficava na versao do arquivo.
Agora passa /p:Version. Em ambos os pontos (script e release.yml) a versao vai
com 3 partes, porque o Directory.Build.props acrescenta o ".0" — passar 4
produziria 5.

O parametro -Version do script continuava [Parameter(Mandatory)], entao o
PowerShell pediria o valor no prompt e o fallback recem-adicionado para o
Directory.Build.props nunca seria alcancado. Removido.

LogStatistics.Compute aceitava buckets/topCount invalidos: buckets = 0 dividiria
por zero e criaria um vetor vazio, com indice negativo em seguida. Agora valida
na entrada, longe da exceção obscura la dentro. 4 testes novos.

OmniVirtualize recebia Height="null" — em Razor isso e a STRING "null", nao um
null. Funcionava por acidente (o browser ignora a altura invalida e o pai
dimensiona, que era a intencao), mas por motivo errado. Corrigido para
Height="@null".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@afernandes
afernandes merged commit c407c7b into main Jul 29, 2026
1 check passed
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