A documentação descrevia um sistema que não existe mais: 62/73 ferramentas
(são 74), writer.py e models.py como arquivos (viraram pacotes), 1032 testes
(são 1454), models_api.py descrito como "API FastAPI" (é ponte JSON) e o app
SwiftUI ausente por completo — 5.500 linhas que o usuário opera todo dia sem
uma linha de documentação.
Cada arquivo passa a ter uma função específica, com cabeçalho de escopo
dizendo o que cobre e o que NÃO cobre (com a seta para quem cobre). O objetivo
é ler só o necessário: doc fora do assunto custa tempo e processamento sem
entregar nada.
01 arquitetura camadas, duas portas de entrada, regras transversais
02 módulos mapa do engine, incluindo o pipeline de voz
03 server/tools as 74 tools, helpers e como criar uma nova
08 app macOS NOVO — build por swiftc, telas, ponte, etapa 5
09 manutenção NOVO — por onde começar, o que está aberto, sintoma→arquivo
CLAUDE.md ganha a seção "Documentação (MANDATORY)": tabela de roteamento
(qual arquivo abrir para cada tarefa) e a regra de que toda alteração de
código atualiza a doc no mesmo commit, com o mapa de o-que-mexeu → o-que-
atualizar. Doc velha engana mais que doc ausente.
O índice do 05_EXPERIENCIAS subiu para o topo: consultar "isso já quebrou
antes?" custava carregar 1.281 linhas antes de chegar na tabela.
Dívidas levantadas na varredura e registradas em 09 §2: etapa 6 ainda ignora
o phrase_review.json, offset de ~400ms do Whisper, MacApp sem teste, admin/
fora do lint, confirmações visuais pendentes no FCP, submódulo WHISPERX sujo.
Também corrigidos dois links quebrados no Engine/README que apontavam um
nível acima do certo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9.2 KiB
08 — O app macOS (MacApp/) e o Assistente
Escopo: O app SwiftUI e o Assistente: build, telas, ponte e a etapa 5. Não cobre: Engine Python (→ 02) · ferramentas MCP (→ 03)
O app SwiftUI é como o usuário opera o sistema sem abrir terminal nem conversar
com uma IA. São ~5.500 linhas em MacApp/Sources/, e ele não tem lógica de
edição: tudo que ele faz é montar argumentos, chamar a ponte Python e mostrar
o resultado.
Última varredura: 2026-08-19
1. Como o app é construído — leia antes de mexer
Não existe .xcodeproj nem Package.swift. O app é compilado invocando o
swiftc direto sobre MacApp/Sources/*.swift:
cd code && ./MacApp/build_app.sh # compila e monta o .app
cd code && ./MacApp/build_app.sh --run # compila e abre
Consequências práticas, todas já sentidas:
- Arquivo novo em
Sources/entra sozinho no build. Não há lista de alvos. - Não dá para adicionar dependência SPM sem antes migrar o build inteiro.
- Compilar não prova que roda. Componentes SwiftUI que embrulham classes
Objective-C podem falhar só em tempo de execução, ao abrir a tela. Foi o que
aconteceu com
VideoPlayer(AVKit): compilava limpo e abortava ao abrir a etapa 5 (05_EXPERIENCIAS.md#22). Por isso a regra: alterou a interface, abra a tela de fato.
Testando uma tela sem navegar o app inteiro
Um harness de vinte linhas compila os mesmos fontes com um @main próprio que
monta só a tela em questão. Reproduz crash de runtime em segundos:
swiftc -parse-as-library -sdk "$(xcrun --sdk macosx --show-sdk-path)" \
-target arm64-apple-macosx26.0 \
MacApp/Sources/PhraseReviewView.swift MacApp/Sources/PhraseReviewModel.swift \
MacApp/Sources/TimelineTracksView.swift MacApp/Sources/Models.swift \
MacApp/Sources/PythonBridge.swift /tmp/HarnessMain.swift -o /tmp/harness
O @main do harness carrega a tela, imprime o que interessa e chama
NSApplication.shared.terminate — dá para afirmar "abriu e funcionou" sem
depender de screenshot.
2. Estrutura das telas
| Arquivo | Linhas | Papel |
|---|---|---|
WizardView.swift |
808 | O Assistente — fluxo guiado de 7 etapas |
TranscriptionView.swift |
843 | Transcrição avulsa e processamento em lote |
ModelDownloadView.swift |
545 | Catálogo e download de modelos Whisper |
CaptionsView.swift |
545 | Legendas dinâmicas: estilo + preview ao vivo |
TimelineTracksView.swift |
506 | Timeline com trilhas, zoom e playhead |
PhraseReviewModel.swift |
429 | Estado da etapa 5: frases, player, zooms |
PhraseReviewView.swift |
413 | Etapa 5: preview + inspector de frases |
VoiceAnalysisView.swift |
322 | Parâmetros do motor de ênfase |
ProjectView.swift |
293 | Inspeção do .fcpxml |
Models.swift |
274 | Espelhos Swift do JSON da ponte |
PythonBridge.swift |
230 | A ponte — ver seção 3 |
SubtitlePreviewView.swift |
218 | Preview 9:16 das legendas |
App.swift |
69 | NavigationSplitView e as abas |
Abas (ActiveTab em App.swift): Assistente · Projeto · Legendas · Análise de
Voz · Modelos · Sobre. As cinco últimas são "Avançado" — atalhos para operações
soltas. O Assistente é o caminho principal.
3. PythonBridge.swift — como o app fala com o Python
O app lança admin/models_api.py como subprocesso, passando o comando e um
JSON como argv, e lê JSON-lines no stdout.
PythonBridge.call(command: "build_phrase_review",
arguments: ["voice_timeline": path]) { result, error in … }
Dois pontos que já causaram problema e estão resolvidos no código — não os desfaça sem entender:
uv runprecisa rodar com cwd emcode/. Ouvescolhe o ambiente pelo diretório do processo, não pelo caminho do script. Rodar da raiz fazia ouvcriar um segundo.venvvazio e ignorar tudo que estava instalado emcode/.venv— librosa e pyannote instalavam com sucesso e o app insistia que faltavam.scriptURLprocuraadmin/models_api.pysubindo diretórios a partir do cwd, do bundle e do home. É o que faz o app funcionar tanto rodando do Xcode quanto do.appmontado.
Para adicionar um comando: função em admin/api/<assunto>.py, registro na
tabela de admin/models_api.py, e PythonBridge.call do lado Swift. Os 37
comandos e seus formatos estão documentados no docstring de models_api.py.
4. O Assistente — as 7 etapas
WizardStep (WizardView.swift) é um enum sequencial; canAdvance decide
quando o botão "Continuar" libera.
| # | Etapa | O que acontece | Comando da ponte |
|---|---|---|---|
| 1 | Projeto | Escolhe a pasta de saída e o .fcpxml |
project_config |
| 2 | Transcrever | Transcreve toda a mídia do projeto | transcribe |
| 3 | Analisar voz | Mede ênfase, locutores, emoção | analyze_voice |
| 4 | Decisões da IA | Copia para o chat, cola o JSON de volta, aplica | apply_voice_actions |
| 5 | Revisar ênfases | Lapida frase a frase — ver seção 5 | build_phrase_review / save_phrase_review |
| 6 | Processar | Silêncios, preenchimento, legendas | vários, em cadeia |
| 7 | Concluído | Abre no FCP ou mostra no Finder | — |
A etapa 4 é a única manual do fluxo, e de propósito: o julgamento de qual
tomada usar e onde dar zoom é conversa com uma IA (skill editar-por-voz), não
um botão. O app monta o pedido pronto no clipboard e recebe o JSON de volta.
Etapa 1 — armadilha registrada: não escolha como "o projeto" um arquivo já
gerado pelo fluxo (_voice_edit, _silence_removed, …). Os cortes de voz
assumem timestamps da mídia original; reaplicá-los sobre um arquivo já
cortado desloca tudo em silêncio. O wizard avisa (looksLikeGeneratedFile).
5. Etapa 5 — a sala de edição
Única tela que ocupa a janela toda: o corpo do wizard é uma coluna de 640pt, e
essa etapa escapa dela porque precisa da largura (step == .revisar em
WizardView.body).
┌────────────────────────────┬──────────────┐
│ Preview (AVPlayerLayer) │ Inspector │
│ enquadrado no formato │ de frases │
│ de entrega do projeto │ │
├────────────────────────────┴──────────────┤
│ Timeline: 6 trilhas, zoom, playhead │
└───────────────────────────────────────────┘
Trilhas: zooms · frases · energia por palavra · emoção · locutor · roteiro/bastidor. Todas desenhadas sobre o mesmo eixo de tempo, com uma coluna fixa à esquerda nomeando cada uma.
O que o usuário decide por frase: nível de ênfase (0–3), ativo/inativo, texto, roteiro/bastidor e o trim das pontas. O trim anda em fronteira de palavra — cortar é apontar para uma palavra, arrastando a borda do bloco ou clicando na palavra no inspector.
Zoom manual: arrastar na timeline marca um trecho; botão direito cria um zoom nele. O zoom guarda só o quando — escala e ramp vêm das configurações de Análise de Voz no momento do render, então mudar lá restiliza todos.
Decisões de implementação que parecem detalhe e não são:
- O preview não renderiza nada. Ele toca a mídia original e pula os trechos removidos. Renderizar para conferir um toggle poria minutos entre a decisão e o resultado. O observador roda a 60 Hz porque o período dele é exatamente quanto de material cortado dá para ouvir antes do pulo.
- O enquadramento é o do projeto, não o da mídia. As gravações são
horizontais e a entrega é vertical; o app lê o formato do
.fcpxml(inspect) e mostra o corte central aproximado, com um selo para alternar para a mídia original. O enquadramento real de cada clipe vem do FCP — o preview é aproximação, e o selo diz isso. - Nada é processado aqui. "Continuar" grava o
_phrase_review.jsone o_phrase_actions.jsonderivado dele. A geração é da etapa 6. - A revisão é sempre remontada da análise atual, com as decisões salvas
reaplicadas por cima (
merge_saved_decisions). Assim refazer a análise de voz melhora a tela em vez de ficar mascarado por uma cópia velha; uma decisão cuja frase se moveu mais de 0,25 s é descartada em vez de colar na frase errada.
6. Estado atual e o que falta
Funciona e foi verificado: carga das frases com decisões da IA, as 6 trilhas, seleção sincronizada nos três painéis, trim por palavra, zoom manual, reprodução parando no ponto exato (erro de 0 ms medido), pulo dos trechos removidos, enquadramento vertical, gravação ao avançar.
Ainda em aberto:
- A etapa 6 não consome o
_phrase_review.json. A ligação — zoom e legenda dinâmica só nas frases de ênfase, legenda comum no resto — é a próxima tarefa. MacApp/não tem teste automatizado. A rede é o harness da seção 1 e o olho do usuário. Toda mudança de interface precisa ser aberta de fato.- O preview aproxima o reenquadramento vertical pelo corte central; se os clipes forem reposicionados no FCP, diverge.