Pular para o conteúdo
Oleaflyv0.4.7

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 (abre em uma nova guia) para o panorama de frontend/Rust/sidecar e Desenvolvimento 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.

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).

PacoteO que éPortas injetadasDependências notáveis
@oleafly/latexLó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 figurasnenhuma (puro)nenhuma
@oleafly/ai-coreMetadados de exibição de provedores e seleção de modelos no frontend, mantidos em paridade com o catálogo Rustnenhuma (puro)nenhuma
@oleafly/latex-intelligenceTipos validados e acesso a dados para o corpus de LaTeX usado pela inteligência do editornenhuma (puro)nenhuma
@oleafly/registryO registro de contribuições: abas da rail, comandos da paleta/omnibar, conjuntos de ferramentas de IA (detalhes abaixo)nenhuma (as contribuições carregam o próprio comportamento)react (tipos)
@oleafly/preflightO 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 nodenenhuma (puro)pdfjs-dist (somente subpath)
@oleafly/pdf-to-latexO 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 nodenenhuma (puro)pdfjs-dist (somente subpath)
@oleafly/wysiwygO 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á-lonenhuma (puro)nenhuma
@oleafly/editorO 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 gramaticalEditorHost (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/previewO 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), setPdfLoggerpdfjs-dist
@oleafly/diagramO 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 sentidosDiagramHost (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-toolsDefinições de ferramentas de projeto, pesquisa, memória e figuras, com mutações que respeitam aprovaçãoAiToolsHost (arquivos, compilação, índice de símbolos, pipeline de figuras, editor)@oleafly/latex
@oleafly/templatesA 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 propsnenhuma além de utilitários de UI

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

Como o shell do app e os pacotes dependem uns dos outrosO shell do app em src/ contém os stores do Zustand, o kit de interface, o cliente Tauri e os adaptadores de portas. Os adaptadores implementam as portas que os pacotes de recursos @oleafly declaram, como EditorHost, e o app os injeta. Os pacotes de recursos podem depender de pacotes folha puros, como latex, ai-core e registry. O cliente Tauri conversa com o backend em Rust por IPC do Tauri. Os pacotes nunca importam o app, os stores dele nem as APIs do Tauri.Shell do app, src/Pacotes @oleafly/*StoresZustandAdaptadoresImplementam as portasKit de interfaceshadcnCliente Taurisrc/lib/tauri.tsPacotes de recursosDeclaram portas como EditorHostPacotes folhalatex, ai-core, registryBackend em Rustsrc-tauri, crates/oleafly-agentInjetaTauri IPCOs pacotes nunca importam o app, os stores dele nem as APIs do Tauri.Como o shell do app e os pacotes dependem uns dos outrosO shell do app em src/ contém os stores do Zustand, o kit de interface, o cliente Tauri e os adaptadores de portas. Os adaptadores implementam as portas que os pacotes de recursos @oleafly declaram, como EditorHost, e o app os injeta. Os pacotes de recursos podem depender de pacotes folha puros, como latex, ai-core e registry. O cliente Tauri conversa com o backend em Rust por IPC do Tauri. Os pacotes nunca importam o app, os stores dele nem as APIs do Tauri.Shell do app, src/Pacotes @oleafly/*StoresKit de interfaceCliente TauriAdaptadoresBackend em Rustsrc-tauri, cratesPacotes de recursosDeclaram as portasPacotes folhalatex, ai-core, registryTauri IPCInjetaOs pacotes nunca importam o app,os stores dele nem as APIs do Tauri.
O app depende dos pacotes e injeta adaptadores para as portas que eles declaram. Os pacotes nunca importam o app.

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:

Janela do terminal
grep -rn 'from "@/\|@tauri\|zustand' packages/*/src # must return nothing

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:

// 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.

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.

@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:

// 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.

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.

  • 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 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.

Visualização da imagem

Não foi possível carregar as respostas da IA

Tente de novo ou pesquise na documentação.