From 938b2c10953002ab091f2b56b41f56af173d568b Mon Sep 17 00:00:00 2001 From: Anderson Fernandes do Nascimento Date: Tue, 28 Jul 2026 12:36:14 -0300 Subject: [PATCH 1/4] Unifica a versao numa fonte unica e limpa residuos (roadmap P4.21/P4.23) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .github/workflows/release.yml | 1 + Directory.Build.props | 27 ++++++++++++++++++++++++++ publish-store-package.ps1 | 14 ++++++++++++- src/ClefExplorer.csproj | 8 ++++---- src/Services/FileAssociationService.cs | 4 +++- 5 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 Directory.Build.props diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cce69c3..46d4107 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -93,6 +93,7 @@ jobs: /p:AppxBundlePlatforms=x64 /p:UapAppxPackageBuildMode=StoreUpload /p:AppxPackageSigningEnabled=false + /p:Version=${{ steps.ver.outputs.version }} - name: Locate upload package id: pkg diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..077b652 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,27 @@ + + + + + 1.1.0 + + $(ClefVersion) + + $(ClefVersion).0 + $(ClefVersion).0 + + + diff --git a/publish-store-package.ps1 b/publish-store-package.ps1 index 8f50191..0b55679 100644 --- a/publish-store-package.ps1 +++ b/publish-store-package.ps1 @@ -39,7 +39,7 @@ [CmdletBinding()] param( [Parameter(Mandatory = $true)] - [string]$Version, + [string]$Version = '', [string]$Platform = 'x64', [string]$Configuration = 'Release', @@ -60,6 +60,18 @@ foreach ($p in @($srcProj, $pkgProj, $manifest)) { if (-not (Test-Path $p)) { throw "Não encontrei: $p (rode o script da raiz do repositório)." } } +# ---------- Versão: parâmetro, ou a do Directory.Build.props ---------- +if ([string]::IsNullOrWhiteSpace($Version)) { + $propsPath = Join-Path $root 'Directory.Build.props' + if (Test-Path $propsPath) { + $Version = ([xml](Get-Content $propsPath)).Project.PropertyGroup.ClefVersion + } + if ([string]::IsNullOrWhiteSpace($Version)) { + throw "Informe -Version ou defina em Directory.Build.props." + } + Write-Host "Versão herdada do Directory.Build.props: $Version" -ForegroundColor DarkGray +} + # ---------- Normaliza a versão para x.y.z.0 ---------- $v = $Version.Trim().TrimStart('v', 'V') if ($v -notmatch '^\d+\.\d+(\.\d+){0,2}$') { diff --git a/src/ClefExplorer.csproj b/src/ClefExplorer.csproj index aa1fd79..3a7d63b 100644 --- a/src/ClefExplorer.csproj +++ b/src/ClefExplorer.csproj @@ -11,7 +11,9 @@ true true - win-x64;win-x86 + + win-x64 true true app.ico @@ -24,9 +26,7 @@ https://github.com/afernandes/ClefExplorer https://github.com/afernandes/ClefExplorer.git log;serilog;clef;viewer;blazor;winforms;structured-logging - 1.0.0 - 1.0.0.0 - 1.0.0.0 + diff --git a/src/Services/FileAssociationService.cs b/src/Services/FileAssociationService.cs index 0c32768..bb0c038 100644 --- a/src/Services/FileAssociationService.cs +++ b/src/Services/FileAssociationService.cs @@ -101,7 +101,9 @@ private void RegisterAppId(string exePath) using var key = Registry.CurrentUser.CreateSubKey($@"Software\Classes\{AppId}"); if (key != null) { - key.SetValue(null, "Reader Log File"); + // Mesmo rótulo declarado no Package.appxmanifest, para o tipo de arquivo + // aparecer igual na versão instalada e na avulsa. + key.SetValue(null, "Clef Log File"); key.SetValue("Icon", $"\"{exePath}\",0"); using var shell = key.CreateSubKey("shell"); From 1259ae5b77746b539f9cafe83a492452b212bf1a Mon Sep 17 00:00:00 2001 From: Anderson Fernandes do Nascimento Date: Tue, 28 Jul 2026 12:42:13 -0300 Subject: [PATCH 2/4] Virtualiza a lista e corrige a paginacao off-by-one (roadmap P4.18) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/Components/LogHeader.razor | 4 ++- src/Components/LogList.razor | 53 ++++++++++++++++++++------------- src/Components/LogToolbar.razor | 7 +++-- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/Components/LogHeader.razor b/src/Components/LogHeader.razor index 6bc245e..af99720 100644 --- a/src/Components/LogHeader.razor +++ b/src/Components/LogHeader.razor @@ -42,7 +42,9 @@ [Parameter] public EventCallback OnReload { get; set; } - private readonly int[] _qtdOptions = { 30, 50, 100 }; + // Páginas grandes só passaram a fazer sentido depois que a lista virou virtualizada: + // antes, 1000 itens significavam 1000 nós no DOM. + private readonly int[] _qtdOptions = { 30, 50, 100, 250, 500, 1000 }; private static string FormatQtd(int v) => $"{v} itens"; diff --git a/src/Components/LogList.razor b/src/Components/LogList.razor index 633497a..350022e 100644 --- a/src/Components/LogList.razor +++ b/src/Components/LogList.razor @@ -9,29 +9,33 @@ Carregando logs... } - else if (Eventos != null && Eventos.Any()) + else if (_itens.Count > 0) { - @foreach (var e in Eventos) - { - var ev = e; - var selected = ReferenceEquals(SelectedEvent, ev); -
-
-
- - @ev.Timestamp?.ToString("HH:mm:ss") -
-
@ev.Message
-
- @if (!string.IsNullOrEmpty(ev.Exception)) - { - Exception - } - @ObterResumoProps(ev) + @* Virtualizado: só as linhas visíveis (mais um pequeno overscan) vão ao DOM. + Height=null porque o pai (.clef-list) já limita a altura e faz o scroll — + sem altura limitada o Virtualize renderizaria tudo. É o que permite páginas + grandes (500/1000) sem a UI travar. *@ + + +
+
+
+ + @ev.Timestamp?.ToString("HH:mm:ss") +
+
@ev.Message
+
+ @if (!string.IsNullOrEmpty(ev.Exception)) + { + Exception + } + @ObterResumoProps(ev) +
-
- } + + } else { @@ -47,6 +51,15 @@ [Parameter] public ClefEvent? SelectedEvent { get; set; } [Parameter] public EventCallback OnSelect { get; set; } + /// + /// O OmniVirtualize exige ICollection: materializa uma vez por mudança de + /// parâmetros, em vez de a cada render. + /// + private ICollection _itens = Array.Empty(); + + protected override void OnParametersSet() => + _itens = Eventos as ICollection ?? Eventos?.ToList() ?? (ICollection)Array.Empty(); + private string ObterResumoProps(ClefEvent e) { if (e.Properties == null) return ""; diff --git a/src/Components/LogToolbar.razor b/src/Components/LogToolbar.razor index d072cae..f5bb27e 100644 --- a/src/Components/LogToolbar.razor +++ b/src/Components/LogToolbar.razor @@ -42,8 +42,11 @@ Title="@(DetailAtBottom ? "Mover o painel de detalhes para a direita" : "Mover o painel de detalhes para baixo")" OnClick="OnToggleDetailPosition" /> } - From a4e1b79832f3bce36a4808a9b41d3120eeb3c89a Mon Sep 17 00:00:00 2001 From: Anderson Fernandes do Nascimento Date: Tue, 28 Jul 2026 12:46:36 -0300 Subject: [PATCH 3/4] Melhora a acessibilidade dos elementos proprios (roadmap P4.20) 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 --- src/Components/LogGroupManager.razor | 10 +++++++-- src/Components/LogList.razor | 31 ++++++++++++++++++++++++++-- src/wwwroot/css/app.css | 19 +++++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/Components/LogGroupManager.razor b/src/Components/LogGroupManager.razor index 7743a66..e25eee3 100644 --- a/src/Components/LogGroupManager.razor +++ b/src/Components/LogGroupManager.razor @@ -11,10 +11,16 @@ @foreach (var group in GroupService.Groups) { var g = group; -
+ @*
+ }
diff --git a/src/Components/LogList.razor b/src/Components/LogList.razor index 350022e..ee91237 100644 --- a/src/Components/LogList.razor +++ b/src/Components/LogList.razor @@ -1,7 +1,9 @@ @using ClefExplorer.Models @using ClefExplorer.Helpers -
+@* role=listbox + option: os itens são
por causa do layout, mas precisam ser + anunciados como uma lista de opções selecionáveis e alcançáveis pelo teclado. *@ +
@if (IsBusy) {
@@ -18,7 +20,12 @@
+ role="option" + tabindex="0" + aria-selected="@ReferenceEquals(SelectedEvent, ev).ToString().ToLowerInvariant()" + aria-label="@RotuloAcessivel(ev)" + @onclick="@(() => OnSelect.InvokeAsync(ev))" + @onkeydown="@(e => AoTeclar(e, ev))">
@@ -60,6 +67,26 @@ protected override void OnParametersSet() => _itens = Eventos as ICollection ?? Eventos?.ToList() ?? (ICollection)Array.Empty(); + /// Enter/Espaço ativam o item, como num botão. + private async Task AoTeclar(KeyboardEventArgs e, ClefEvent ev) + { + if (e.Key is "Enter" or " ") + { + await OnSelect.InvokeAsync(ev); + } + } + + /// + /// Nome anunciado pelo leitor de tela. Sem ele, o item seria lido como um amontoado de + /// textos soltos — e o nível, que visualmente é um badge colorido, se perderia. + /// + private static string RotuloAcessivel(ClefEvent e) + { + var hora = e.Timestamp?.ToString("HH:mm:ss") ?? ""; + var excecao = string.IsNullOrEmpty(e.Exception) ? "" : ", com exceção"; + return $"{e.Level} às {hora}: {e.Message}{excecao}"; + } + private string ObterResumoProps(ClefEvent e) { if (e.Properties == null) return ""; diff --git a/src/wwwroot/css/app.css b/src/wwwroot/css/app.css index b82b505..3a9226f 100644 --- a/src/wwwroot/css/app.css +++ b/src/wwwroot/css/app.css @@ -552,15 +552,21 @@ span.omni-tree-text.clef-tree-backup { font-weight: 600; } flex-direction: column; gap: 8px; } +/* É um