# 09 — Manutenção: onde mexer, o que está aberto, o que dói > **Escopo:** Por onde começar cada tipo de tarefa, o que está aberto e onde dói. > **Não cobre:** Como as coisas funcionam — este doc roteia para quem explica Este é o documento de rota. Os outros descrevem o que **é**; este diz o que **fazer** e por onde começar quando chega uma implementação, uma melhoria ou uma correção. Última varredura: 2026-08-21 · 1.498 testes · lint zerado (fora de server.py/ai_edit.py/llm_local.py, pré-existentes) --- ## 1. Chegou uma tarefa — por onde começo? | A tarefa é… | Comece em | Não esqueça | |-------------|-----------|-------------| | Regra nova de edição (corte, zoom, legenda) | `fcpxml/` + teste | Expor na tool **e** na ponte, senão só metade dos usuários alcança | | Corrigir XML que o FCP recusa | `fcpxml/writer/` + `dtd.py` | Validar contra o DTD real, não só o teste | | Mudança visível na interface | `MacApp/Sources/` | **Abrir a tela** — compilar não prova nada (§4) | | Comando novo para o app | `admin/api/.py` | Registrar na tabela de `models_api.py` | | Ferramenta MCP nova | `server_tools/.py` | Schema `Tool(...)` + `TOOL_HANDLERS` | | Ajuste de análise de voz | `fcpxml/voice_*`, `emphasis.py` | Regerar os `_voice_timeline.json` de teste | | "Está lento" / "está errado" e não sei onde | §5 (mapa de sintomas) | — | **A pergunta que resolve 90% das dúvidas de lugar:** essa lógica precisa saber o que é uma tool MCP ou uma tela? Se não precisa — e quase nunca precisa — ela vai para `fcpxml/`. --- ## 2. O que está aberto agora Ordenado por quanto atrapalha, não por esforço. ### 2.1 `resolve_actions` não tolera margem no encosto de zoom/marker contra um corte Um `zoom`/`marker` cuja borda cai exatamente em cima do `start`/`end` de um `cut` é descartado como "apontando para material cortado" — mesmo quando a intenção era ficar bem ao lado. Contornado manualmente no projeto Mastopexia (recuando as bordas na mão); a correção estrutural é dar a `resolve_actions` uma margem de tolerância (meio frame) antes de considerar uma ação "dentro" do corte. → `fcpxml/voice_actions.py` (`resolve_actions`/`shift_after_cuts`), `05_EXPERIENCIAS.md` #27. ### 2.2 `MacApp/` não tem teste automatizado 5.500 linhas de Swift sem uma asserção. A rede hoje é o harness manual (§4) e o olho do usuário. Não é para sair criando suíte de UI — mas lógica pura que foi parar na camada de tela (cálculo de trim, mapeamento de tempo) deveria descer para o Python, onde já existe rede. ### 2.3 `admin/` fica fora do lint `run_after_fix.sh` roda o ruff de dentro de `code/`, então `admin/` — 1.751 linhas de código que o app depende para funcionar — nunca é verificado. Incluir mexe no gate, então é decisão consciente, não esquecimento. ### 2.4 Confirmações visuais pendentes no FCP Várias entradas do `05_EXPERIENCIAS.md` estão marcadas como resolvidas *no XML* — testes verdes, DTD válido — mas **pendentes de importação real no Final Cut**. XML válido não é o mesmo que XML que renderiza como o esperado. Ao mexer em legenda, zoom ou keyframe, a confirmação final é abrir no FCP. ### 2.5 `remove_media_silence` deixa fatias sub-segundo nas emendas entre clipes Mesmo depois de corrigir o merge de cortes consecutivos (`05_EXPERIENCIAS.md` #28), sobraram 4 clipes de 0,07-0,23s no projeto Mastopexia real, todos bem na emenda entre dois clipes vizinhos — mesma família do #6 (clipe-fantasma de 1 frame por padding sem vizinho na borda), mas não confirmado se é a mesma causa raiz. Não investigado a fundo ainda. → `fcpxml/writer/cut.py` (`cut_clip_ranges`, `min_keep_seconds`), padding do `remove_media_silence`. ### 2.6 Submódulo `WHISPERX` com conteúdo modificado e não commitado Está fora dos commits de propósito, porque ninguém verificou o que mudou lá dentro. Precisa ser olhado e resolvido — ou commitado, ou revertido. --- ## 3. Onde o código ainda é grande (e onde isso não é problema) Quatro arquivos foram divididos (`writer.py`, `models.py`, `models_api.py`, `_shared.py`): 6.685 linhas concentradas viraram 43 módulos. O que sobrou grande, e o diagnóstico honesto de cada um: | Arquivo | Linhas | Vale dividir? | |---------|-------:|---------------| | `fcpxml/text_layout.py` | 901 | **Não.** É diagramação — um assunto coeso. | | `fcpxml/rough_cut.py` | 798 | **Não.** É geração de timeline, um assunto. | | `fcpxml/model_manager.py` | 748 | Talvez: mistura catálogo, download e config. | | `server_tools/voice.py` | 754 | Talvez, se crescer mais. | | `MacApp/TranscriptionView.swift` | 843 | Sim, quando for mexer nela. | | `MacApp/WizardView.swift` | 808 | Sim: sete etapas num `switch` só. | **Critério, não número:** divida quando o arquivo tiver **assuntos** que não se falam. Um arquivo grande de um assunto só é mais fácil de ler que seis arquivos pequenos que você precisa abrir juntos. Código picado sem motivo atrapalha tanto quanto arquivo gigante. --- ## 4. Checklist antes de dar algo por pronto ```bash cd code && ./Engine/run_after_fix.sh # lint zerado + 1.498 testes admin/run_app.command # se mexeu no app (padrão de revisão) ``` E, além do script: - [ ] **Mexeu na interface? Abriu a tela?** Compilar não prova que roda — `VideoPlayer` compilava e abortava (`05_EXPERIENCIAS.md` #22). - [ ] **Mexeu em XML? Importou no FCP?** DTD válido ≠ renderiza certo. - [ ] **Dividiu ou moveu módulo?** Procure `patch('.` e imports relativos dentro de funções — é o que quebra em silêncio (#23). - [ ] **Criou teste fora de `code/tests/`?** Confirme que a contagem total subiu. Teste fora de `testpaths` não roda e dá falsa sensação de rede (#24). - [ ] **Problema estrutural ou erro recorrente?** Registre em `05_EXPERIENCIAS.md` com o índice atualizado. - [ ] **Documentação divergiu?** Corrija no mesmo commit. Doc velha engana mais que doc ausente. --- ## 5. Mapa de sintomas → onde olhar | Sintoma | Suspeite de | Arquivo | |---------|-------------|---------| | FCP recusa o arquivo ao importar | `id` inválido, ordem de filhos, timebase | `writer/validation.py`, `dtd.py` | | Título importa mas não aparece | Template/uid Motion que não resolve | `writer/titles.py` | | Corte no lugar errado | Tempo pós-corte usado como se fosse original | `voice_actions.py` (`shift_after_cuts`) | | Zoom/marker sumindo perto de um corte | Borda encostando exatamente no `cut` | §2.1 | | Legenda sobrepondo | Layout ou conteúdo antigo no arquivo | `collision.py`, `text_layout.py` | | "Ênfase" apontando para palavra à toa | Falta renormalizar após o corte | `refine_voice_timeline` | | App diz que falta librosa/pyannote | `uv run` com cwd errado | `PythonBridge.swift` (§3 do doc 08) | | App crasha com `ModuleNotFoundError: server_tools` | `sys.path` de `admin/api/` mal calculado | `05_EXPERIENCIAS.md` #25 | | Tela do app fecha o programa | Componente de framework que só falha em runtime | `05_EXPERIENCIAS.md` #22 | | Comando existe no MCP mas não no app | Falta expor na ponte | `admin/api/`, #20 | --- ## 6. Convenções que não são negociáveis Estão em `01_ARCHITECTURE.md` §2 e valem repetir as três que mais custaram: 1. **Tempo é fração racional.** Float para tempo produz drift que só aparece depois de dez operações encadeadas. 2. **Ação de voz é sempre em tempo da mídia original.** Nunca pós-corte. 3. **Original nunca é sobrescrito.** Toda saída ganha sufixo. --- ## Documentos relacionados - [01 Arquitetura](01_ARCHITECTURE.md) — camadas e onde cada coisa mora - [02 Módulos](02_MODULES.md) — mapa do engine, módulo a módulo - [03 Server/Tools](03_SERVER_TOOLS.md) — as 77 ferramentas MCP - [04 Testes & Workflow](04_TESTS_AND_WORKFLOW.md) - [05 Experiências](05_EXPERIENCIAS.md) — o que já quebrou e por quê - [06 Boas Práticas](06_BOAS_PRATICAS.md) - [08 App macOS](08_APP_MACOS.md) — o app e o Assistente