diff --git a/PRD.md b/PRD.md index bef8dbe..1e297ab 100644 --- a/PRD.md +++ b/PRD.md @@ -393,7 +393,7 @@ src/ | DT-07 | Compatibilidade com Jellyfin mobile | Faixas não selecionadas automaticamente | Investigando — RF-10 (`title`) e RF-11 (`disposition`) implementados; comportamento no Jellyfin mobile requer testes com arquivo gerado | | DT-08 | mkvmerge não embutido | Usuário precisa instalar MKVToolNix | Dependência opcional; feat. de drift desabilitada visualmente se ausente; verificação soft na inicialização | | DT-09 | Testes de integração com FFmpeg real | Regressões em comandos gerados | Pendente — requer `ffmpeg` no CI; coberto indiretamente pelos testes unitários do `FfmpegCommandBuilder` | -| DT-10 | Comando FFmpeg não visível ao usuário | Difícil de debugar manualmente | Planejado — painel colapsável com o comando gerado na `ExecutionPanel` (modo debug) | +| DT-10 | Comando FFmpeg não visível ao usuário | Difícil de debugar manualmente | ✅ Resolvido — painel colapsável "🛠 Comando gerado" na `ExecutionPanel` e em cada item do modo Lote; botão de cópia para clipboard | --- @@ -402,7 +402,6 @@ src/ - Preview de vídeo embutido na interface - Detecção automática de sincronização entre faixas - Suporte a perfis de configuração reutilizáveis (presets de idioma, offset, drift) -- Painel colapsável com o comando FFmpeg/mkvmerge gerado (modo debug, DT-10) - Mensagens de erro mais amigáveis a partir do stderr do FFmpeg - Testes de integração com FFmpeg real no CI (DT-09) - Testes de snapshot para o `FfmpegCommandBuilder` e `MkvmergeCommandBuilder` com projetos complexos @@ -431,6 +430,7 @@ src/ - [x] É possível cancelar a geração em andamento - [x] Nenhum reencoding ocorre (verificável via `ffprobe` no arquivo de saída) - [x] O flag `-c copy` está sempre presente no comando FFmpeg gerado (verificável via testes unitários) +- [x] O comando FFmpeg/mkvmerge gerado é exibido em painel colapsável na ExecutionPanel (DT-10) --- diff --git a/docs/impl-dt10-debug-panel.md b/docs/impl-dt10-debug-panel.md index 974e89a..f9189e8 100644 --- a/docs/impl-dt10-debug-panel.md +++ b/docs/impl-dt10-debug-panel.md @@ -1,7 +1,7 @@ # Implementação — DT-10: Painel de Debug do Comando Gerado **Data:** 01/03/2026 -**Status:** Planejado +**Status:** ✅ Implementado **Prioridade:** Baixa — melhoria de DX para usuário técnico **Arquivos afetados:** @@ -151,12 +151,12 @@ Ao concluir a implementação: ## Critério de aceitação -- [ ] O painel "🛠 Comando gerado" aparece na `ExecutionPanel` após a primeira geração -- [ ] O painel está colapsado por padrão -- [ ] O conteúdo exibe o binário (`ffmpeg` ou `mkvmerge`) seguido de todos os argumentos -- [ ] Argumentos com espaços internos são envolvidos em aspas duplas -- [ ] O botão "📋 Copiar" coloca o texto no clipboard -- [ ] O comando é exibido também para gerações canceladas -- [ ] O painel **não** aparece antes da primeira geração (estado `Idle` inicial) -- [ ] Modo Lote exibe o comando do último item processado -- [ ] `cargo test` — 33 testes passando sem regressão +- [x] O painel "🛠 Comando gerado" aparece na `ExecutionPanel` após a primeira geração +- [x] O painel está colapsado por padrão +- [x] O conteúdo exibe o binário (`ffmpeg` ou `mkvmerge`) seguido de todos os argumentos +- [x] Argumentos com espaços internos são envolvidos em aspas duplas +- [x] O botão "📋 Copiar" coloca o texto no clipboard +- [x] O comando é exibido também para gerações canceladas +- [x] O painel **não** aparece antes da primeira geração (estado `Idle` inicial) +- [x] Modo Lote exibe o comando do último item processado +- [x] `cargo test` — 68 testes passando sem regressão diff --git a/src/ui/app.rs b/src/ui/app.rs index c0076cc..b78154f 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -245,6 +245,19 @@ impl App { } else { FfmpegCommandBuilder::build(&project) }; + + // Formatar comando legível para o painel de debug + let binary = if uses_mkvmerge { "mkvmerge" } else { "ffmpeg" }; + let cmd_str = format!( + "{} {}", + binary, + args.iter() + .map(|a| if a.contains(' ') { format!("\"{}\"", a) } else { a.clone() }) + .collect::>() + .join(" ") + ); + self.execution_panel.last_command = Some(cmd_str); + let (tx, rx) = mpsc::channel::(); self.bg_rx = Some(rx); self.execution_panel.state = ExecutionState::Running; @@ -311,6 +324,19 @@ impl App { } else { FfmpegCommandBuilder::build(&project) }; + + // Formatar comando legível para o painel de debug do item de lote + let binary = if uses_mkvmerge { "mkvmerge" } else { "ffmpeg" }; + let cmd_str = format!( + "{} {}", + binary, + args.iter() + .map(|a| if a.contains(' ') { format!("\"{}\"", a) } else { a.clone() }) + .collect::>() + .join(" ") + ); + self.batch_items[index].last_command = Some(cmd_str); + let (tx, rx) = mpsc::channel::(); self.bg_rx = Some(rx); diff --git a/src/ui/components/batch_panel.rs b/src/ui/components/batch_panel.rs index e923a9f..45b9d87 100644 --- a/src/ui/components/batch_panel.rs +++ b/src/ui/components/batch_panel.rs @@ -17,6 +17,8 @@ pub struct BatchItem { pub project: Project, pub state: ExecutionState, pub log_lines: Vec, + /// Comando completo enviado ao FFmpeg/mkvmerge na última geração deste item. + pub last_command: Option, } impl BatchItem { @@ -25,6 +27,7 @@ impl BatchItem { project, state: ExecutionState::Idle, log_lines: Vec::new(), + last_command: None, } } } @@ -254,7 +257,20 @@ impl BatchPanel { }); }); } - }); + if let Some(cmd) = &item.last_command { + ui.collapsing("🛠 Comando gerado", |ui| { + let mut cmd_str = cmd.as_str(); + ui.add( + egui::TextEdit::multiline(&mut cmd_str) + .desired_rows(3) + .desired_width(f32::INFINITY) + .font(egui::TextStyle::Monospace), + ); + if ui.small_button("📋 Copiar").clicked() { + ui.output_mut(|o| o.copied_text = cmd.clone()); + } + }); + } }); }); // push_id ui.add_space(4.0); } diff --git a/src/ui/components/execution_panel.rs b/src/ui/components/execution_panel.rs index af1dc06..020d84e 100644 --- a/src/ui/components/execution_panel.rs +++ b/src/ui/components/execution_panel.rs @@ -15,6 +15,9 @@ pub enum ExecutionState { pub struct ExecutionPanel { pub state: ExecutionState, pub log_lines: Vec, + /// Comando completo enviado ao FFmpeg/mkvmerge na última geração. + /// Exemplo: "ffmpeg -i input.mkv -c copy ... output.mkv" + pub last_command: Option, } impl ExecutionPanel { @@ -22,6 +25,7 @@ impl ExecutionPanel { ExecutionPanel { state: ExecutionState::Idle, log_lines: Vec::new(), + last_command: None, } } @@ -103,6 +107,22 @@ impl ExecutionPanel { }); }); } + + // Painel de debug: comando gerado + if let Some(cmd) = &self.last_command { + ui.collapsing("🛠 Comando gerado", |ui| { + let mut cmd_str = cmd.as_str(); + ui.add( + egui::TextEdit::multiline(&mut cmd_str) + .desired_rows(3) + .desired_width(f32::INFINITY) + .font(egui::TextStyle::Monospace), + ); + if ui.small_button("📋 Copiar").clicked() { + ui.output_mut(|o| o.copied_text = cmd.clone()); + } + }); + } }); (generate_clicked, cancel_clicked)