Documento de Arquitetura e Decisões Técnicas Parte do ecossistema acerola — GPL3
O acerola-desktop é o cliente desktop multiplataforma do ecossistema acerola. O usuário abre o app, navega pela biblioteca local de mangás, lê arquivos .cbz e .cbr, converte .pdf para esses formatos, e opcionalmente conecta via plugin ao acerola-translator para tradução automática com IA.
O projeto faz parte de um ecossistema maior:
android-acerola — leitor Android existente (Kotlin, GPL3)acerola-translator — serviço de tradução com IA (Go + Python, GPL3)acerola-desktop — este projeto (Rust + Tauri + Svelte, GPL3)acerola-web-reader — leitor web futuro (GPL3)O desktop é um cliente independente — funciona sem o translator. O plugin de tradução é opcional e configurável para apontar para uma instância selfhost ou para o servidor pago do ecossistema acerola.
Dado um arquivo .cbz, .cbr ou .pdf na máquina local do usuário:
.pdf para .cbz preservando qualidade das imagens| Camada | Tecnologia | Responsabilidade |
|---|---|---|
| Shell nativo | Rust + Tauri v2 | Janela, sistema de arquivos, bridge frontend↔OS |
| Leitura de arquivos | Rust (zip, unrar, pdfium-render) | CBZ, CBR, PDF — extração de páginas |
| Cache | Rust (lru) | LRU por bytes — páginas em memória |
| Servidor local | Rust (axum) | Streaming de páginas via HTTP para celular |
| Descoberta de rede | Rust (mdns-sd) | Descobre translator e celular na rede local |
| Frontend | Svelte 5 + TypeScript | UI, leitor, biblioteca, configurações |
| Estilo | Tailwind CSS + shadcn-svelte | Componentes e design system |
| Plugin de tradução | HTTP client → acerola-translator | Envia CBZ, recebe progresso SSE, baixa resultado |
| Assinatura de binário | SignPath Foundation | Assinatura gratuita para projetos GPL3 — Windows |
O critério decisivo não foi performance nem tamanho de binário — foi ecossistema e facilidade de pesquisa. Para um projeto onde o desenvolvedor quer aprender fazendo — pesquisar "como fazer X" e achar um exemplo bom rapidamente — o Tauri entrega isso de forma consistente. O Wails falha nesse ponto em casos menos comuns.
O Rust do desktop é mínimo e contido. Todo o código Rust se resume a: abrir arquivos, gerenciar cache, expor funções ao Svelte via #[tauri::command], e subir o servidor axum. A lógica pesada — OCR, inpainting, tradução — fica no acerola-translator. O borrow checker é o maior obstáculo no início, mas o escopo é pequeno o suficiente para aprender sem frustração excessiva.
O paradigma funcional do Rust também é familiar para quem vem de Kotlin com Arrow-kt — Result<T, E>, Option<T>, map, and_then, imutabilidade por padrão. O modelo mental já existe, só a sintaxe muda.
sequenceDiagram
participant U as Usuário
participant S as Svelte
participant R as Rust
participant C as Cache LRU
U->>S: abre arquivo
S->>R: invoke("open_file", path)
R->>C: page existe no cache?
alt cache hit
C-->>R: bytes da página
else cache miss
R->>R: lê do disco (CBZ/CBR/PDF)
R->>C: armazena no cache
end
R-->>S: bytes da página
S-->>U: renderiza imagem
Note over R,C: tokio::spawn — prefetch N+1, N+2, N+3 em background
sequenceDiagram
participant D as Desktop (axum)
participant M as mDNS
participant A as android-acerola
Note over D: startup — sobe servidor axum :8080
D->>M: anuncia serviço na rede local
A->>M: descobre serviços acerola
M-->>A: desktop em 192.168.x.x:8080
A->>D: GET /library
D-->>A: lista de mangás (JSON)
A->>D: GET /manga/{id}/page/{n}
D-->>A: bytes da página
Note over A: renderiza como se fosse arquivo local
sequenceDiagram
participant S as Svelte
participant R as Rust
participant T as acerola-translator
S->>R: invoke("translate", {path, endpoint})
R->>T: POST /api/upload (.cbz)
T-->>R: jobId
R->>T: GET /api/events (SSE)
loop para cada página
T-->>R: SSE: {page, status}
R-->>S: emit("progress", {page, status})
S-->>S: atualiza UI em tempo real
end
T-->>R: SSE: done
R->>T: GET /api/download/:jobId
T-->>R: .cbz traduzido
R->>R: salva na biblioteca local
R-->>S: emit("done", path)
| Opção | Prós | Contras | Veredicto |
|---|---|---|---|
| Tauri v2 | Melhor docs, Rust robusto, binário ~10MB, Svelte nativo | Rust tem curva de aprendizado | ✅ Escolhido |
| Wails + Go | Go simples, compartilha stack com translator | Ecossistema menor, exemplos escassos | ❌ Docs insuficientes para o fluxo de pesquisa desejado |
| Electron | Enorme ecossistema Node | Binário ~150MB, RAM alta | ❌ Pesado demais para leitor de mangá |
| Flutter Desktop | Bons componentes, null safety | Dart é linguagem extra sem benefício claro | ❌ Stack desnecessariamente diferente |
| Opção | Prós | Contras | Veredicto |
|---|---|---|---|
| zip (crate) | Puro Rust, rápido, sem dependência nativa | Só ZIP — CBZ é ZIP, funciona perfeitamente | ✅ Escolhido para CBZ |
| unrar (crate) | Lê RAR nativamente | Wrapper de lib C — requer libunrar | ✅ Escolhido para CBR |
| pdfium-render | Renderiza PDF com alta fidelidade, mantido pelo Google | Binário nativo pdfium incluído (~30MB) | ✅ Escolhido para PDF |
| pdf-extract | Puro Rust | Qualidade de renderização inferior para imagens | ❌ Descartado |
| Opção | Prós | Contras | Veredicto |
|---|---|---|---|
| lru (crate) por bytes | LRU por tamanho real — evita estourar RAM com páginas grandes | Implementação ligeiramente mais complexa | ✅ Escolhido |
| lru por quantidade | Simples | Página 4K ocupa muito mais que página pequena — limite impreciso | ❌ Descartado |
| Sem cache | Zero complexidade | Leitor trava a cada troca de página | ❌ Inaceitável |
| Opção | Prós | Contras | Veredicto |
|---|---|---|---|
| axum | Async nativo Rust, tokio, ergonômico, bem documentado | Nenhuma desvantagem relevante | ✅ Escolhido |
| tiny-http | Minimalista | Síncrono — bloqueia em leituras grandes | ❌ Descartado |
| warp | Async, bom ecossistema | axum tem docs melhores e é mais mantido atualmente | ❌ Preterido |
| Opção | Plataforma | Custo | Veredicto |
|---|---|---|---|
| SignPath Foundation | Windows | Gratuito para projetos GPL3 open source | ✅ Escolhido para Windows |
| Apple Developer Program | macOS | $99/ano — sem alternativa gratuita | ⏸️ Adiado — macOS não é prioridade inicial |
| Certum Open Source | Windows | ~$50-80/ano | ❌ Preterido — SignPath é gratuito |
| EV Certificate | Windows | $300-500/ano | ❌ Caro demais para projeto indie |
| Sem assinatura | Linux | Gratuito — AppImage/Flatpak não exige | ✅ Linux não precisa |
O SignPath Foundation assina binários Windows gratuitamente para projetos com licença OSI-aprovada sem dual-licensing comercial. O acerola-desktop é GPL3 puro e se qualifica. O modelo de negócio — cobrar pela hospedagem do servidor — não conflita com os termos porque o binário assinado é 100% open source. O servidor pago é infraestrutura separada, não código dentro do binário.
| Opção | Prós | Contras | Veredicto |
|---|---|---|---|
| Svelte 5 + Tailwind + shadcn-svelte | Svelte é o mais simples para UI customizada, Tailwind acelera estilo, shadcn-svelte tem componentes modernos customizáveis | Svelte 5 runes é sintaxe nova | ✅ Escolhido |
| React + shadcn/ui | Enorme ecossistema | Verboso, boilerplate excessivo para uma pessoa | ❌ Descartado |
| Vue 3 | Menos verboso que React | Menor ecossistema de componentes que shadcn-svelte | ❌ Descartado |
acerola-desktop/
├── src-tauri/
│ ├── src/
│ │ ├── main.rs ← entrypoint Tauri
│ │ ├── commands/ ← funções expostas ao Svelte
│ │ │ ├── library.rs ← escaneia pasta, lista mangás
│ │ │ ├── reader.rs ← abre arquivo, retorna página
│ │ │ ├── converter.rs ← PDF → CBZ
│ │ │ └── translator.rs ← plugin: upload, SSE, download
│ │ ├── archive/
│ │ │ ├── cbz.rs ← lê ZIP (CBZ)
│ │ │ ├── cbr.rs ← lê RAR (CBR)
│ │ │ └── pdf.rs ← renderiza PDF via pdfium-render
│ │ ├── cache/
│ │ │ └── page_cache.rs ← LRU por bytes, prefetch
│ │ ├── server/
│ │ │ ├── routes.rs ← axum: /library, /manga/:id/page/:n
│ │ │ └── mdns.rs ← anuncia serviço na rede local
│ │ └── state.rs ← AppState compartilhado entre commands
│ ├── Cargo.toml
│ └── tauri.conf.json
├── src/
│ ├── lib/
│ │ ├── Reader.svelte ← leitor de páginas
│ │ ├── Library.svelte ← grade de mangás
│ │ ├── Progress.svelte ← progresso de tradução (SSE)
│ │ └── Settings.svelte ← endpoint do translator
│ ├── App.svelte
│ └── app.css
├── package.json
└── vite.config.ts
Uma página de mangá pode variar de 200KB a 8MB dependendo da resolução e formato. Um cache de "50 páginas" pode consumir 400MB ou 4GB — impossível prever. O cache por bytes define um limite real (ex: 200MB) e despeja as páginas menos recentes quando o limite é atingido, independente de quantas páginas isso representa.
Quando o usuário está na página N, o Rust já carrega N+1, N+2, N+3 em background via tokio::spawn. O usuário nunca espera — a próxima página já está no cache quando ele vira. Esse padrão transforma um leitor "aceitável" em um leitor que parece nativo.
#[tauri::command]
async fn prefetch_pages(path: String, current: usize, state: State<'_, AppState>) {
for i in 1..=3 {
let path = path.clone();
let state = state.inner().clone();
tokio::spawn(async move {
// carrega em paralelo sem bloquear a UI
});
}
}
O servidor axum não serve só para o celular conectar — ele também é a interface para operações pesadas que não cabem bem em comandos Tauri síncronos. Conversões de PDF longas retornam progresso via SSE pelo mesmo servidor, sem travar a UI.
O plugin não tem lógica de provedor — ele armazena apenas uma URL base. Selfhost aponta para localhost:8080, servidor pago aponta para api.acerola.app. O protocolo é idêntico nos dois casos. Trocar de um para o outro é mudar uma string nas configurações.
Todo o Rust escrito no desktop se resume a abrir arquivos, gerenciar cache, expor funções via #[tauri::command], e subir o servidor axum. A lógica pesada fica no acerola-translator. O escopo pequeno torna o borrow checker gerenciável para quem está aprendendo.
A licença GPL3 que poderia parecer limitante para o modelo de negócio é exatamente o que qualifica o projeto para assinatura gratuita no Windows. O modelo correto é: software open source, serviço pago. Nextcloud, Jellyfin e Immich operam da mesma forma.
| Fase | O que implementar | O que aprende |
|---|---|---|
| 1 | Tauri hello world — janela abre, Svelte renderiza | Setup Tauri, estrutura de projeto, bridge Rust↔Svelte |
| 2 | Abrir CBZ, listar páginas no frontend via comando Tauri | crate zip, #[tauri::command], Svelte 5 runes básico |
| 3 | Renderizar páginas no leitor — navegação básica | Transferência de bytes Rust→Svelte, exibição de imagem |
| 4 | Cache LRU por bytes + prefetch das próximas páginas | crate lru, tokio::spawn, AppState com Mutex |
| 5 | Abrir CBR | crate unrar, lidar com formato binário diferente |
| 6 | Converter PDF → CBZ | pdfium-render, pipeline de conversão, progresso via SSE |
| 7 | Biblioteca local — escanear pasta, persistir metadados | walkdir, serde + serde_json, fs nativo |
| 8 | Servidor axum + mDNS — celular conecta e lê | axum, tokio, mdns-sd, streaming HTTP |
| 9 | Plugin de tradução — upload, SSE, download | reqwest, SSE client, configuração de endpoint |
| 10 | UI completa com shadcn-svelte + Tailwind | Svelte 5 runes avançado, design system |
| 11 | SignPath Foundation — configurar assinatura Windows | CI/CD, GitHub Actions, code signing pipeline |
O desktop foi desenhado para ser um nó do ecossistema, não um app isolado. Ele consome e serve ao mesmo tempo:
GET /library — lista os mangás da biblioteca localGET /manga/{id}/page/{n} — serve uma página para qualquer cliente na redePOST /api/upload → acerola-translator — envia para traduçãoGET /api/events → acerola-translator — recebe progresso em tempo real| Projeto | Linguagem | Consome o desktop? | Consome o translator? |
|---|---|---|---|
| android-acerola | Kotlin | Sim — streaming HTTP local | Sim — plugin HTTP |
| acerola-desktop | Rust + Tauri + Svelte | É o servidor local | Sim — plugin HTTP |
| acerola-translator | Go + Python | Não | É o serviço |
| acerola-web-reader | A definir | Sim — mesmo endpoint | Sim — mesmo plugin |
O software é 100% open source (GPL3). A monetização é via hospedagem:
api.acerola.app nos dois appsO binário assinado pelo SignPath Foundation é o mesmo nos dois cenários — apenas a URL muda.
Se o Rust clicar bem no acerola-desktop, o acerola-translator pode ser portado de Go para Rust — reduzindo o ecossistema de 4 linguagens (Rust, Go, Python, TypeScript) para 3 (Rust, Python, TypeScript). O Python permanece por conta do manga-ocr, que não tem alternativa equivalente fora do ecossistema Python de ML.
acerola-desktop — documento gerado a partir de sessão de arquitetura Licença GPL3 — parte do ecossistema acerola