Skip to content

Roadmap P2: drag & drop, tail ao vivo, exportação, instância única e estado da janela - #5

Merged
afernandes merged 4 commits into
fix/roadmap-p0-p1from
feat/roadmap-p2
Jul 28, 2026
Merged

Roadmap P2: drag & drop, tail ao vivo, exportação, instância única e estado da janela#5
afernandes merged 4 commits into
fix/roadmap-p0-p1from
feat/roadmap-p2

Conversation

@afernandes

Copy link
Copy Markdown
Owner

Resumo

Implementa as features essenciais (P2) do ROADMAP.md, mais o endurecimento de permissões do WebView2 (item 17, que caiu na mesma área do código).

Base: este PR sai de fix/roadmap-p0-p1 (#4) — revise aquele primeiro. Total acumulado: 121 testes.

O que entrou

7. Arrastar e soltar arquivos/pastas

O README anunciava a funcionalidade, mas ela não existia. O detalhe não óbvio: o WebView2 engole o drop por padrão, e a API web não expõe o caminho completo do arquivo. Desligando AllowExternalDrop, o evento chega ao formulário, que recebe os caminhos reais via DataFormats.FileDrop. Aceita arquivos e pastas misturados.

8. Modo "Ao vivo" (tail)

O app carregava um snapshot estático — acompanhar uma aplicação rodando exigia reabrir o arquivo na mão, justamente o caso de uso mais comum durante um debug. Agora um botão na toolbar liga a sondagem: a cada segundo o LogStoreapenas o que foi acrescentado desde a última leitura (offset por arquivo).

Optei por sondagem em vez de FileSystemWatcher (o roadmap sugeria o watcher): ele perde eventos quando o buffer estoura e depende de o escritor atualizar os metadados, o que loggers com buffer nem sempre fazem na hora. Comparar o tamanho do arquivo é barato, previsível e já naturalmente debounced.

Casos cobertos por teste: linha ainda sendo escrita é ignorada (JSON incompleto envenenaria o parser), arquivo truncado/rotacionado recomeça do zero, eventos já carregados não são relidos, .gz é pulado, padrões de linha ignorada continuam valendo.

9. Exportar eventos filtrados

Só existia "copiar stack trace" de um evento. Agora um botão exporta o conjunto filtrado inteiro — não a página exibida, que é o que o usuário vê mas raramente o que quer compartilhar. Formato deduzido da extensão:

  • CSV com escape RFC 4180 (campo entre aspas quando contém vírgula, aspas ou quebra de linha — o caso comum de stack traces);
  • CLEF, de modo que o arquivo exportado possa ser reaberto pelo próprio ClefExplorer;
  • texto puro.

10. Instância única + múltiplos arquivos

Program.cs usava apenas args[0]: selecionar cinco .clef no Explorer abria cinco janelas. Agora todos os argumentos são carregados, e um mutex nomeado + named pipe garantem uma janela por usuário — instâncias seguintes entregam seus caminhos à existente e encerram. Se o mutex falhar, o app segue como instância independente (perder o "instância única" é melhor que não abrir).

11. Estado da janela

Tamanho/posição/maximizado preservados entre execuções (antes: sempre 1200x800). Ao restaurar, valida se a posição ainda cabe em algum monitor conectado — sem isso, desconectar um monitor secundário faria a janela reabrir fora da área visível.

17. Permissões do WebView2

PermissionRequested liberava tudo (câmera, microfone, geolocalização…). Passa a negar por padrão, permitindo apenas ClipboardRead, usada pelo botão "copiar stack trace".

Mudança de suporte no LogStore

Novo evento PathsLoaded, disparado apenas quando um conjunto novo de caminhos é carregado — e não quando o usuário (des)marca arquivos na árvore. Assim a UI limpa a seleção de arquivos visíveis nas cargas externas (drop, linha de comando, outra instância) sem desestabilizar a árvore durante a marcação, que foi a causa de um bug anterior.

Verificação

  • 121/121 testes verdes; 0 erros e 0 avisos CS* no build.
  • Validado no app real (screenshots durante o desenvolvimento):
    • abrir dois .clef de uma vez → uma janela com os dois carregados;
    • lançar uma segunda instância → nenhuma janela nova; o arquivo é carregado na existente;
    • mover/redimensionar → fechar → reabrir restaura exatamente em 260,140 900x560;
    • window.json gravado limpo (sem a propriedade calculada).
  • Não validado na UI: o clique no botão Exportar e o drop de arquivos com o mouse — a máquina bloqueou a tela durante os testes finais. A lógica de exportação está coberta por 20 testes unitários e o caminho do drop compila e registra no log se o AllowExternalDrop falhar (não falhou).

Notas para o revisor

  • O Timer do tail é qualificado como System.Threading.Timer: o System.Windows.Forms.Timer também está em escopo e exigiria a bomba de mensagens da UI.
  • A leitura incremental corta no último \n; como esse byte não aparece no meio de uma sequência UTF-8 multibyte, o corte nunca parte um caractere.
  • O item 11 cobre a janela; as larguras dos splitters continuam pendentes (anotado no roadmap).
  • Restam os itens P3 (atalhos, filtros avançados, performance, cancelamento, prompt()) e P4.

🤖 Generated with Claude Code

afernandes and others added 4 commits July 28, 2026 01:44
…/P2.11)

Arrastar e soltar (P2.7): o README anunciava a funcionalidade, mas ela nao
existia. O WebView2 engole o drop por padrao e a API web nao expoe o caminho
completo do arquivo; desabilitando AllowExternalDrop, o evento chega ao
formulario, que le os caminhos reais via DataFormats.FileDrop. Aceita arquivos
e pastas misturados, encaminhando para LoadFromPathsAsync.

Instancia unica e multiplos arquivos (P2.10): Program.cs usava apenas args[0],
entao selecionar cinco .clef no Explorer abria cinco janelas, cada uma com um
arquivo. Agora todos os argumentos sao carregados e um mutex nomeado + named
pipe garantem uma janela por usuario: instancias seguintes entregam seus
caminhos a existente, trazem a janela para frente e encerram. Se o mutex
falhar, o app segue como instancia independente — perder o "instancia unica" e
melhor do que nao abrir.

Estado da janela (P2.11): tamanho/posicao/maximizado passam a ser preservados
entre execucoes (antes abria sempre em 1200x800). Ao restaurar, valida se a
posicao ainda cabe em algum monitor conectado — sem isso, desconectar um
monitor secundario faria a janela reabrir fora da area visivel. Guarda
RestoreBounds quando maximizada, para o tamanho "normal" sobreviver.

Permissoes do WebView2 (P3.17, aproveitando a mesma area): PermissionRequested
liberava TUDO (camera, microfone, geolocalizacao...). Passa a negar por padrao,
permitindo apenas ClipboardRead, usada pelo botao "copiar stack trace".

LogStore ganha o evento PathsLoaded, disparado apenas quando um conjunto NOVO
de caminhos e carregado — e nao quando o usuario (des)marca arquivos na arvore.
Assim a UI limpa a selecao de arquivos visiveis nas cargas externas (drop,
linha de comando, outra instancia) sem desestabilizar a arvore durante a
marcacao, que foi a causa de um bug anterior.

10 testes novos para a validacao de monitores e a persistencia do placement.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nao havia como tirar nada do app: so existia 'copiar stack trace' no detalhe.
Depois de montar um filtro util (ex.: todos os erros de uma correlacao), o
usuario nao conseguia compartilhar o resultado.

Botao Exportar na toolbar, que exporta o conjunto FILTRADO inteiro — nao
apenas a pagina exibida, que e o que o usuario ve mas raramente o que quer
compartilhar. O formato vem da extensao escolhida no dialogo:

- CSV com escape RFC 4180 (aspas duplicadas; campo entre aspas quando contem
  virgula, aspas ou quebra de linha — o caso comum de stack traces).
- CLEF (um JSON por linha), de modo que o arquivo exportado possa ser reaberto
  pelo proprio ClefExplorer. Omite '@l' para Information, prefere o
  MessageTemplate ao texto renderizado e mantem acentos legiveis.
- Texto puro, uma linha por evento com a excecao abaixo.

IFilePickerService ganha PickSaveFileAsync. A serializacao roda em background
e o resultado vira notificacao de sucesso ou de erro.

20 testes cobrindo escape do CSV, formato CLEF, texto e a deducao do formato
pela extensao.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
O app carregava um snapshot estatico: acompanhar uma aplicacao rodando exigia
reabrir o arquivo na mao — justamente o caso de uso mais comum durante um
debug.

Botao 'Ao vivo' na toolbar. Quando ligado, o LogStore sonda os arquivos
carregados a cada segundo e le apenas o que foi acrescentado desde a ultima
leitura (offset por arquivo), em vez de reprocessar tudo.

Optei por sondagem em vez de FileSystemWatcher: o watcher perde eventos quando
o buffer estoura e depende de o escritor atualizar os metadados, o que loggers
com buffer nem sempre fazem na hora. Comparar o tamanho do arquivo e barato e
previsivel — e ja e naturalmente debounced.

Detalhes que os testes cobrem:
- so consome ate a ultima quebra de linha: uma linha ainda sendo escrita e
  JSON incompleto e envenenaria o parser;
- arquivo menor que o offset (rotacao/truncamento) recomeca do zero;
- eventos ja carregados nao sao relidos;
- .gz e ignorado (arquivo fechado, nao cresce);
- os padroes de linha ignorada continuam valendo no modo ao vivo;
- uma varredura por vez, para tiques nao empilharem leituras.

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:56
@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 o conjunto de features essenciais (P2) do roadmap do ClefExplorer e aproveita para endurecer permissões do WebView2, adicionando também cobertura de testes para as novas capacidades (tail, exportação e persistência/validação de posicionamento da janela).

Changes:

  • Adiciona modo “Ao vivo” (tail) no LogStore com leitura incremental por offset e polling.
  • Implementa exportação do conjunto filtrado (CSV/CLEF/texto) e integra na toolbar.
  • Adiciona instância única, suporte a múltiplos caminhos via args / outra instância / drag&drop, e persistência de estado da janela (com validação contra monitores atuais) + hardening de permissões do WebView2.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
test/ClefExplorer.Tests/WindowPlacementTests.cs Testes para persistência e validação de visibilidade do placement da janela.
test/ClefExplorer.Tests/LogStoreTests.cs Novos testes cobrindo o modo tail (linhas novas, truncamento, ignorados, etc.).
test/ClefExplorer.Tests/LogExporterTests.cs Testes de contrato para exportação em CSV/CLEF/texto e seleção de formato.
src/wwwroot/css/app.css Estilo para ações à direita na toolbar (exportar + paginação).
src/Services/WinFormsFilePickerService.cs Adiciona “save file picker” para suportar exportação.
src/Services/WindowPlacementService.cs Serviço de load/save do placement e validação contra monitores.
src/Services/SingleInstance.cs Implementação de instância única com mutex + named pipe.
src/Services/LogStore.cs Evento PathsLoaded e implementação do tail com offsets e timer.
src/Services/LogExporter.cs Serialização para CSV/CLEF/texto e helpers de formato/extensão.
src/Services/IFilePickerService.cs Novo método PickSaveFileAsync para exportação.
src/Program.cs Integra instância única, múltiplos args e registra WindowPlacementService.
src/Models/WindowPlacement.cs Modelo persistido do placement (X/Y/W/H/Maximized) + regra IsUsable.
src/MainForm.cs Drag&drop, recebimento de paths de outra instância, window placement, WebView2 permissions.
src/Components/LogViewer.razor Botões “Ao vivo” e “Exportar”, reset de seleção via PathsLoaded, lógica de export.
src/Components/LogToolbar.razor UI da toolbar com “Ao vivo”, “Exportar” e paginação.
ROADMAP.md Marca P2 como concluído e atualiza status/registro do roadmap.

💡 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
Comment thread src/Components/LogViewer.razor
Comment thread test/ClefExplorer.Tests/LogStoreTests.cs
@afernandes
afernandes merged commit 2c7ac55 into fix/roadmap-p0-p1 Jul 28, 2026
1 check passed
afernandes added a commit that referenced this pull request Jul 28, 2026
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>
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