# Implementação — DT-10: Painel de Debug do Comando Gerado **Data:** 01/03/2026 **Status:** ✅ Implementado **Prioridade:** Baixa — melhoria de DX para usuário técnico **Arquivos afetados:** - `src/ui/components/execution_panel.rs` - `src/ui/app.rs` - `PRD.md` (documentação ao concluir) --- ## Objetivo Exibir, de forma colapsável na `ExecutionPanel`, o comando exato (`ffmpeg …` ou `mkvmerge …`) que foi enviado ao processo externo na última geração. Permite que o usuário técnico inspecione, copie e reproduza o comando manualmente para diagnóstico. --- ## Viabilidade | Ponto | Situação | | ------------------------------------------------ | ------------------------------------------------ | --- | ---------------------------------- | | `args: Vec` já construído antes do spawn | `start_generation()` — linha ~244 de `app.rs` | | `ExecutionPanel` já usa `ui.collapsing()` | Padrão reutilizado do "Log detalhado" | | Nenhuma camada nova necessária | Mudanças em 2 arquivos apenas | | Sem impacto em testes existentes | Campo additive — nenhuma assinatura pública muda | | Botão "Copiar" sem dependência adicional | `ui.output_mut( | o | o.copied_text = …)` nativo do egui | **Estimativa:** ~30 linhas adicionadas. Risco zero de regressão. --- ## Passo 1 — Campo `last_command` em `ExecutionPanel` **Arquivo:** `src/ui/components/execution_panel.rs` Adicionar o campo à struct: ```rust 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, } ``` Inicializar em `ExecutionPanel::new()`: ```rust pub fn new() -> Self { ExecutionPanel { state: ExecutionState::Idle, log_lines: Vec::new(), last_command: None, } } ``` --- ## Passo 2 — Formatar e armazenar o comando em `app.rs` **Arquivo:** `src/ui/app.rs` **Localização:** método `start_generation()`, logo após construir `args` e antes do `thread::spawn` ```rust // 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); ``` > **Atenção:** replicar o mesmo bloco em `start_batch_generation()` para cobrir o modo Lote (RF-09). --- ## Passo 3 — Renderizar o painel colapsável em `ExecutionPanel::ui()` **Arquivo:** `src/ui/components/execution_panel.rs` **Localização:** após o bloco `"Log detalhado"`, antes do `});` de fechamento do `ui.group` ```rust 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()); } }); } ``` **Comportamento esperado:** - Colapsável fechado por padrão — não ocupa espaço visual desnecessário - `TextEdit` somente leitura (variável local imutável no contexto do egui) - Botão "📋 Copiar" coloca o comando no clipboard do sistema - Argumento com espaço interno é envolvido em aspas para reprodutibilidade no terminal --- ## Passo 4 — Comportamento de limpeza `last_command` **não** é limpo ao iniciar nova geração — ele é sobrescrito. O usuário vê sempre o comando da execução atual. `last_command` **não** é limpo em `cancel_generation()` — o usuário pode querer inspecionar o comando de uma geração cancelada. Não há necessidade de resetar em nenhum outro ponto. --- ## Passo 5 — Atualizar PRD e débitos técnicos Ao concluir a implementação: 1. Marcar DT-10 como `✅ Resolvido` na tabela da seção 10 do `PRD.md` 2. Remover o item do backlog da seção 11 3. Adicionar critério de aceitação na seção 12: - `[x] O comando FFmpeg/mkvmerge gerado é exibido em painel colapsável na ExecutionPanel (DT-10)` --- ## Ordem de execução recomendada | # | Passo | Testável após | | --- | --------------------------------------------------------------------- | ---------------------------------- | | 1 | Adicionar campo + init em `ExecutionPanel` | `cargo check` | | 2 | Renderizar painel colapsável (Passo 3) com valor hardcoded temporário | `cargo run` — inspeção visual | | 3 | Wiring em `start_generation()` (Passo 2) | `cargo run` — geração real | | 4 | Replicar wiring em `start_batch_generation()` | `cargo run` — modo lote | | 5 | Remover hardcode temporário se usado; `cargo test` | 33 testes devem continuar passando | | 6 | Atualizar PRD (Passo 5) | — | --- ## Critério de aceitaçã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