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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ffaebb3f72
commit
dcdd73edb5
@@ -0,0 +1,195 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user