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

196 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`:
```bash
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:
```bash
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.
```swift
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.