# Arquitetura do frontend

Como o Oleafly divide o aplicativo de desktop entre o workspace de frontend, os serviços em Rust e os sidecars fixados.

O frontend do Oleafly é um workspace pnpm: o shell do aplicativo fica na raiz do
repositório (`src/`), e o código de frontend reutilizável fica em `packages/*`,
como doze pacotes focados. O backend Tauri fica em `src-tauri/`, enquanto o
código reutilizável de provedores de IA e de agentes fica em
`crates/oleafly-agent/`. Veja o [diagrama de arquitetura do
README](https://github.com/Oleafly/Oleafly/blob/main/README.md#architecture)
para o panorama de frontend/Rust/sidecar e
[Desenvolvimento](/docs/engineering/development/) para os comandos do dia a dia.

Este documento cobre: o que é cada pacote, as regras de inversão de dependência
que os mantêm puros, o registro de contribuições que conecta funcionalidades ao
shell, como funciona a resolução de módulos e como adicionar ou extrair um
pacote.

## Por que um workspace

Antes, cada funcionalidade acessava diretamente os stores Zustand do aplicativo,
o cliente IPC do Tauri e o kit de UI shadcn. Isso tornava as funcionalidades
inseparáveis do shell: nada podia ser testado, reutilizado ou analisado
isoladamente. O workspace inverte isso. Os pacotes concentram a lógica das
funcionalidades e declaram interfaces estreitas, chamadas portas, para tudo de
que precisam. O aplicativo implementa essas portas sobre seus stores e o cliente
Tauri, e as injeta. O aplicativo depende dos pacotes, nunca o contrário.

Os pacotes são consumidos **como código-fonte TypeScript** (o `main` deles
aponta para `src/index.ts`). Não há etapa de build, nem publicação, nem
divergência de versões: Vite, Vitest e `tsc` resolvem `@oleafly/*` diretamente
para as fontes dos pacotes por meio de aliases (veja [Resolução de
módulos](#module-resolution)).

## Os pacotes

| Pacote | O que é | Portas injetadas | Dependências notáveis |
| --- | --- | --- | --- |
| `@oleafly/latex` | Lógica pura de LaTeX/figuras: o modelo de diagramas, o serializador TikZ (`modelToTikz`, ida e volta de modelos embutidos), o construtor de documentos standalone (`buildStandaloneDoc`), auxiliares de nome/bytes de figuras | nenhuma (puro) | nenhuma |
| `@oleafly/ai-core` | Metadados de exibição de provedores e seleção de modelos no frontend, mantidos em paridade com o catálogo Rust | nenhuma (puro) | nenhuma |
| `@oleafly/latex-intelligence` | Tipos validados e acesso a dados para o corpus de LaTeX usado pela inteligência do editor | nenhuma (puro) | nenhuma |
| `@oleafly/registry` | O registro de contribuições: abas da rail, comandos da paleta/omnibar, conjuntos de ferramentas de IA ([detalhes abaixo](#the-contribution-registry)) | nenhuma (as contribuições carregam o próprio comportamento) | react (tipos) |
| `@oleafly/preflight` | O motor de pré-verificação: tipagem de documentos, regras de fonte/PDF/referências, parsing de ATS, preparação de exportação acessível, pontuação. `pdf-extract` é distribuído como o subpath `@oleafly/preflight/pdf-extract` para que o pdf.js fique fora dos grafos de teste em node | nenhuma (puro) | `pdfjs-dist` (somente subpath) |
| `@oleafly/pdf-to-latex` | O pipeline local de reconstrução de PDF para LaTeX: agrupamento de linhas, detecção de títulos e colunas, inferência de estilo, mapeamento de símbolos, extração de figuras, escape de caracteres e montagem de documentos. O adaptador do pdf.js é distribuído como o subpath `@oleafly/pdf-to-latex/pdf-adapter` pelo mesmo motivo do grafo de teste em node | nenhuma (puro) | `pdfjs-dist` (somente subpath) |
| `@oleafly/wysiwyg` | O núcleo do editor visual restrito: esquema compartilhado, parse/serialização de LaTeX e Markdown e os nós brutos inline/bloco que mantêm o código não suportado visível em vez de descartá-lo | nenhuma (puro) | nenhuma |
| `@oleafly/editor` | O núcleo CodeMirror para LaTeX, Typst e Markdown: `CodeMirrorEditor`, o controlador da visualização do editor (`insertAtCursor`, `gotoLine`, …), linguagens + completions, tema, folding, cabeçalhos sticky, linters, latex-mask, pré-visualização de matemática (KaTeX para LaTeX, Typst para Typst), auxílios de edição de Typst e conversão de matemática de LaTeX para Typst, o modo Visual in-loco para LaTeX e Typst (`@oleafly/editor/visual`), painel de busca, linters ortográfico e gramatical | `EditorHost` (modelo de documento + configurações, em formato de hook), `SpellHost` (verificador ortográfico/Harper/dicionário), `setBibKeysProvider`, `extraExtensions`/`extraKeymap` | `@codemirror/*`, `@replit/codemirror-vim`, `@replit/codemirror-emacs`, `katex` |
| `@oleafly/preview` | O visualizador virtualizado em pdf.js (`PdfViewer`), o controlador de páginas do SyncTeX (`gotoRect`, `pageClickToBp`), o worker do pdf.js e os polyfills de WebView (`@oleafly/preview/polyfills`, importado primeiro em `main.tsx`) | prop `onOpenLink` (links no navegador do sistema), `setPdfLogger` | `pdfjs-dist` |
| `@oleafly/diagram` | O compositor visual de diagramas para TikZ, Typst (fletcher) e Mermaid: canvas React Flow, inspetor de formas, abas Desenhar/Código, fluxo de compilar-pré-visualizar-inserir. Cada linguagem em `src/languages/` tem um writer, um reader e seus formulários de inserção e de arquivo, e o canvas e o código permanecem sincronizados nos dois sentidos | `DiagramHost` (compilação LaTeX, renderização de Typst e Mermaid, E/S de arquivos, inserção no editor, correção por IA), contexto `DiagramKit` (Button/Tooltip/Select/toast/theme) | `@xyflow/react`, `@codemirror/*`, `@oleafly/latex`, `@oleafly/editor` |
| `@oleafly/ai-tools` | Definições de ferramentas de projeto, pesquisa, memória e figuras, com mutações que respeitam aprovação | `AiToolsHost` (arquivos, compilação, índice de símbolos, pipeline de figuras, editor) | `@oleafly/latex` |
| `@oleafly/templates` | A galeria de modelos para novos projetos (assistente em duas etapas, categorias, pré-visualizações, downloads únicos de assets com progresso) | `TemplatesHost` (pré-visualizações, downloads de assets, logging), `TemplatesKit` (Button/Tooltip), opções de cor como props | nenhuma além de utilitários de UI |

Direção das dependências, imposta de forma estrita:

Um pacote nunca deve importar de `src/` (`@/…`), de um store Zustand, de
`@tauri-apps/*` ou dos componentes de UI do aplicativo. A verificação de pureza
faz parte da higiene de extração:

```bash
grep -rn 'from "@/\|@tauri\|zustand' packages/*/src   # must return nothing
```

## O padrão de portas

Os pacotes declaram interfaces. O aplicativo as implementa uma única vez,
geralmente como um objeto adaptador em nível de módulo que encapsula seus
stores. Quatro formas recorrentes:

**Portas de host**: um `interface XHost` por pacote cobrindo os serviços de que
ele precisa. O aplicativo constrói o adaptador com leituras imperativas dos
stores:

```ts
// src/lib/ai-tools.ts (app adapter, abridged)
const HOST: AiToolsHost = {
  getProjectId: () => useFilesStore.getState().projectId,
  readFileContent,                       // Tauri client passthrough
  recompile: () => useCompileStore.getState().recompile(),
  insertAtCursor,                        // editor controller
  // ...
};
export const createOleaflyTools = (opts?) => createOleaflyToolsCore(HOST, opts);
```

Manter as preocupações exclusivas do aplicativo dentro do adaptador também
mantém dependências fora dos pacotes. Credenciais e requisições de provedores
ficam no Rust, então os pacotes do frontend recebem apenas o estado e os
resultados que suas interfaces exigem.

**Kits de UI**: um contexto React (ou prop) de componentes com tipagem
estrutural: `Button: ComponentType<{ variant?: …; onClick?: () => void }>`. O
aplicativo repassa seus componentes shadcn sem alterações, porque o tipo
estrutural menor é compatível. Os kits também carregam `toast` e um hook
`useThemeMode()`.

**Portas em formato de hook**: para pacotes que precisam *assinar* o estado do
aplicativo. O `EditorHost` do editor tem membros em formato de hook
(`useActivePath()`, `useSettings()`) mais gêmeos imperativos (`getActivePath()`)
para ouvintes de eventos. Regras: o objeto host deve ficar em nível de módulo
(identidade estável) e seus membros `use*` devem chamar hooks incondicionalmente.

**Singletons de módulo**: para a cola transversal, `setPdfLogger(fn)`,
`setSpellHost(host)` e `setBibKeysProvider(fn)` têm padrões vazios (no-op) e são
instalados pelo shim do aplicativo como efeito colateral de importação.

### Shims

Cada extração deixou um arquivo fino no antigo caminho `@/…`, que reexporta (ou
encapsula) o pacote, de modo que consumidores e caminhos existentes de
`vi.mock(...)` nunca precisaram mudar. Exemplos:
`src/components/editor/cm/controller.ts` (reexportação),
`src/components/pdf/PdfViewer.tsx` (wrapper que injeta o abridor de links do
plugin do shell), `src/lib/ai-tools.ts` (adaptador + assinaturas originais da
fábrica). Código novo pode importar `@oleafly/*` diretamente. Os shims existem
por compatibilidade, não como o caminho preferido.

## O registro de contribuições

`@oleafly/registry` é a forma como as funcionalidades se conectam ao shell do
aplicativo sem que o shell as conheça. Três tipos de contribuição:

- **Abas da rail** (`registerRailTab`): ícone, seção (`explore` / `review` /
  `assist`, divisores desenhados entre seções não vazias), visibilidade opcional
  com `when(ctx)`, hook opcional `useBadge()` (por exemplo, a contagem de
  alterações do Git) e o componente `panel` da barra lateral.
- **Comandos** (`registerCommand`): servidos à paleta de comandos
  (Cmd+K, agrupados por `group`) e/ou ao omnibar (Cmd+P)
  via `surfaces`. Rótulos e ícones podem ser funções do contexto (por exemplo,
  "Mudar para o tema claro").
- **Conjuntos de ferramentas de IA** (`registerAiToolset`): um modo de chat
  (`"chat"`, `"figure"`) mapeado para uma fábrica de conjunto de ferramentas. O
  painel de chat procura o conjunto de ferramentas do modo ativo em vez de
  deixar um fixado no código.

O `AppContext` (`{ projectId, projectKind, theme }`) é construído por cada
superfície de renderização a partir de suas próprias assinaturas, então as
condições de `when`/`label` permanecem reativas.

O registro é **estático**: `src/contributions/` registra cada item embutido
(abas, comandos, conjuntos de ferramentas) e `main.tsx` chama
`registerContributions()` uma vez antes de o shell ser montado. `Rail`,
`Sidebar`, `CommandPalette`, `SearchOmnibar` e `ChatPanel` renderizam apenas o
que está registrado.

Para adicionar a aba ou o comando de uma funcionalidade, registre-o. Sem edições
no shell:

```ts
// src/contributions/my-feature.tsx (called from src/contributions/index.ts)
registerRailTab({
  id: "review", label: "Review", icon: MessageSquare,
  section: "review", order: 55,
  when: (ctx) => ctx.projectKind !== "image",
  panel: ReviewPanel,
});
registerCommand({
  id: "omnibar.review", surfaces: ["omnibar"], order: 45,
  label: "Open review", keywords: "review comments",
  run: () => useSettingsStore.getState().setRailTab("review"),
});
```

Esta é uma API interna, não um SDK de plugins: as formas podem mudar livremente,
e nada é carregado dinamicamente. Adiado até existir um consumidor real:
formatos do menu de exportação, esquemas de configurações e comportamentos por
tipo de projeto como contribuições.

## Resolução de módulos

Como os pacotes são consumidos como código-fonte, o mesmo alias é declarado em
três lugares, e os três devem permanecer em sincronia quando um pacote é
adicionado:

1. `tsconfig.json` → `compilerOptions.paths`. Subcaminhos como
   `@oleafly/preflight/pdf-extract` precisam de uma entrada explícita própria.
2. `vite.config.ts` → `resolve.alias`. Aliases de string casam por prefixo,
   então os subcaminhos resolvem sem outra entrada.
3. `vitest.config.ts` → o mesmo `resolve.alias` e, **além disso**, `test.include`
   deve cobrir `packages/**/*.test.ts`. Caso contrário, testes movidos saem
   silenciosamente da execução.

Além disso: o `package.json` da raiz depende de cada pacote via
`"workspace:*"`, e `src/styles/globals.css` tem `@source "../../packages"` para
que o Tailwind v4 examine as fontes dos pacotes em busca de nomes de classes.

Imports exclusivos do Vite (workers `?worker&url`, imports de CSS) funcionam
sem alterações a partir das fontes dos pacotes, porque os aliases apontam para o
código-fonte. Mantenha fora de barrels qualquer coisa que arraste o pdf.js para
testes que rodam em ambiente node: é por isso que `pdf-extract` e o visualizador
do pacote preview só são alcançáveis onde o DOM existe.

## Trabalhando nos pacotes

- **Testes** ficam ao lado do código-fonte (`packages/*/src/*.test.ts`) e rodam
  no mesmo `pnpm test` dos testes do aplicativo. Observe a *contagem* de testes
  ao mover arquivos.
- **Dependências** são declaradas de forma flexível (`"react": "*"`). O pnpm faz
  a deduplicação contra a raiz, onde ficam os pisos reais de versão. Não declare
  dependências que o pacote não importa.
- **Gates para qualquer alteração de pacote**: `pnpm exec tsc --noEmit`,
  `pnpm build`, `pnpm test` (contagem inalterada) e o grep de pureza acima.
- **Extrair algo novo**: mova os arquivos com `git mv` (o histórico acompanha),
  defina as portas, deixe shims nos caminhos antigos, conecte os três aliases e
  rode os gates. Não extraia uma funcionalidade enquanto itera ativamente sobre
  ela.
- **Licenciamento**: cada pacote é `AGPL-3.0-or-later`, igual ao aplicativo.

## O que fica no aplicativo

O `src/` mantém tudo o que é genuinamente o aplicativo de frontend: os stores
Zustand, o cliente IPC do Tauri (`src/lib/tauri.ts`), o kit de UI shadcn,
componentes de layout/shell, os adaptadores de portas e os shims,
`src/contributions/` e a cola de funcionalidades que por natureza é acoplada a
stores (a UI do painel de pré-verificação, o plugin de edição com IA embutida e
a inteligência de código ou de hover injetada no editor como `extraExtensions`).
Comandos do Tauri e a integração com o sistema operacional ficam em `src-tauri/`.
Clientes de provedores, streaming e orquestração de agentes pertencem a
`crates/oleafly-agent/`, para que segredos e requisições a provedores permaneçam
fora da webview.
