🧩 Filosofia Unix-style
OpenHuman não é um monolito grande — é um conjunto de domínios pequenos
que conversam por fronteiras explícitas. Domain logic vive em src/openhuman/<dominio>/,
transporte vive em src/core/, e ninguém invade ninguém.
⚓ Os três princípios
- •Módulos pequenos, sharp-responsibility: uma coisa, bem feita, fronteira clara.
- •Tests before the next layer: unit antes de RPC, RPC antes de UI. Untested = incompleto.
- •Docs with code: rustdoc + comentários atualizados no mesmo PR.
AGENTS.mdquando regras mudam.
✓ Faz
- ✓Novo domínio em
src/openhuman/<dom>/mod.rs+ irmãos - ✓Controller no registry, schemas em
schemas.rs - ✓mod.rs só exporta; lógica em
ops.rs/store.rs - ✓Arquivos ≤500 linhas; quebra quando crescer
✗ Não faz
- ✗Novo
*.rsstandalone no root desrc/openhuman/ - ✗Branches por domínio em
src/core/cli.rsoujsonrpc.rs - ✗Lógica pesada em
src/core/(é só transporte) - ✗Arquitetura paralela porque "fica mais fácil"
📐 Os 6 passos do workflow
A sequência não é negociável. Pular um passo (ex: ir direto pra UI) gera retrabalho garantido — porque a UI estará chamando uma RPC que nunca foi validada.
Specify contra o codebase atual
Sem nada escrito ainda
Ler domínios existentes, padrões de naming RPC (openhuman.<ns>_<fn>), capability catalog. Nada de inventar.
Implementar em Rust
Domínio + schemas + unit tests
Lógica em src/openhuman/<dom>/, schemas registrados, unit tests passando em isolamento antes de continuar.
JSON-RPC E2E
Prova de contrato pelo transporte
Estender tests/json_rpc_e2e.rs. Os métodos RPC devem casar exatamente com o que a UI vai chamar.
UI no app Tauri
Telas + estado, chamando o core
React + Redux Toolkit + coreRpcClient. Regras ficam no core, UI só orquestra e apresenta.
App unit tests (Vitest)
Comportamento de componentes e hooks
Co-localizados com o código, focados em comportamento visível ao usuário, não em internals.
App E2E (WDIO)
Fluxo end-to-end no app empacotado
Spec em app/test/e2e/specs/. Asserta efeito visível e estado do mock. Não é testável? Spec está incompleta.
🦀 Anatomia de um domínio Rust
Cada domínio segue a mesma estrutura. Saber o nome de cada arquivo é metade do trabalho — a outra metade é o conteúdo certo em cada um deles.
src/openhuman/cron/ ├── mod.rs // só exporta — `mod schemas; mod ops; ...` ├── types.rs // structs/enums do domínio ├── store.rs // persistência ├── ops.rs // operações de negócio ├── rpc.rs // handlers JSON-RPC ├── schemas.rs // ControllerSchema + registro └── bus.rs // EventHandler — <Purpose>Subscriber
💡 Dica do controller migration
Em schemas.rs, exporte:
- •
schemas— lista deControllerSchema - •
all_controller_schemas+all_registered_controllers - •
handle_*fns delegando pararpc.rs
No mod.rs re-exporte como all_<domain>_controller_schemas. Em src/core/all.rs, plug-in. Nunca adicione branch em src/core/dispatch.rs.
📊 Domínios já existentes (referência)
Antes de criar novo domínio, veja se cabe em um existente:
+ ~30 outros. Total: ~50 domínios. Reuso > criação.
🔌 Provando o contrato via JSON-RPC
O contrato RPC é a fronteira mais frágil do projeto: TypeScript de um lado, Rust do outro, serialização no meio. Bug aqui é silencioso até virar runtime error no usuário.
# Suite completa (mock backend up automaticamente) $ pnpm test:rust # Um teste específico $ bash scripts/test-rust-with-mock.sh --test json_rpc_e2e # Via debug runner — output bounded, log completo em target/debug-logs/ $ pnpm debug rust json_rpc_e2e
⚠️ Erros silenciosos comuns
- • Campo opcional no schema, mas a UI envia sempre — schema permissivo demais.
- • Serde rename diferente entre request e response — vira
nullno cliente. - •
RpcOutcome::Errsem ser tratado na UI — usuário vê tela em branco. - • Falta de auth bearer — 401 sem mensagem amigável.
🖥️ Surfando na UI sem virar peso
A UI não decide — orquestra. Toda regra de negócio fica no core. A UI faz três coisas: chamar RPC, apresentar resultado e capturar input do usuário.
✓ UI saudável
- ✓Componente pega dado via
useQuery/Redux +coreRpcClient - ✓Toda string passa por
useT()(i18n) - ✓
invoke('core_rpc_relay', ...)em vez defetch() - ✓Static imports only (no
import())
✗ UI doente
- ✗Regra de negócio em TS (vai duplicar com o core)
- ✗
localStoragead-hoc em vez de Redux persist - ✗String hardcoded em
label=ouaria-label= - ✗
React.lazy(() => import(...))em produção
🔑 Por que core_rpc_relay?
O fetch() dispararia CORS preflight contra o servidor local. invoke('core_rpc_relay', ...) passa pela ponte IPC do Tauri — sem preflight, com bearer já injetado, e funciona idêntico em dev e produção.
📋 Capability catalog & planning
Quando a feature adiciona, remove ou renomeia algo visível ao usuário, atualize o capability catalog no mesmo PR. É o índice central das capacidades do app — sem isso, agentes e documentação ficam desatualizados em silêncio.
📚 Onde mexer
- •
src/openhuman/about_app/— capability catalog - •
AGENTS.md— quando regras/contratos mudam - •
gitbooks/developing/architecture*.md— narrativa estrutural - •rustdoc dos módulos que ganharam comportamento novo
🎯 Planning rule
Antes de codar, defina os cenários E2E que cobrem:
- • Happy path
- • Failure modes (rede, auth, dados inválidos)
- • Auth gates (usuário sem credencial, sessão expirada)
- • Regressões conhecidas do domínio
Se não dá pra testar end-to-end, a spec está incompleta ou o cut está grande demais. Quebre o PR.
🎓 Resumo do Módulo
Próximo Módulo:
5.2 — Testes em 3 níveis (Vitest, WDIO, cargo + mock)