Files
gart/code/Engine/docs/08_APP_MACOS.md
T
João HenriqueandClaude Opus 5 dcdd73edb5 docs: varredura geral, documentação por função e regra de atualização
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>
2026-08-19 22:51:33 -04:00

9.2 KiB
Raw Blame History

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 run precisa rodar com cwd em code/. O uv escolhe o ambiente pelo diretório do processo, não pelo caminho do script. Rodar da raiz fazia o uv criar um segundo .venv vazio e ignorar tudo que estava instalado em code/.venv — librosa e pyannote instalavam com sucesso e o app insistia que faltavam.
  • scriptURL procura admin/models_api.py subindo diretórios a partir do cwd, do bundle e do home. É o que faz o app funcionar tanto rodando do Xcode quanto do .app montado.

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.json e o _phrase_actions.json derivado 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.