Ir al contenido
Oleaflyv0.4.7

Cómo reparte Oleafly la aplicación de escritorio entre el espacio de trabajo del frontend, los servicios en Rust y los sidecars con versión fija.

El frontend de Oleafly es un espacio de trabajo de pnpm: el shell de la aplicación vive en la raíz del repositorio (src/) y el código de frontend reutilizable vive en packages/* como doce paquetes con un propósito concreto. El backend de Tauri vive en src-tauri/, mientras que el código reutilizable de proveedores de IA y agentes vive en crates/oleafly-agent/. Consulta el diagrama de arquitectura del README (se abre en una pestaña nueva) para ver el reparto entre frontend, Rust y sidecars, y Desarrollo para los comandos del día a día.

Este documento explica qué es cada paquete, las reglas de inversión de dependencias que los mantienen puros, el registro de contribuciones que conecta las funciones con el shell, cómo funciona la resolución de módulos y cómo añadir o extraer un paquete.

Antes, cada función accedía directamente a los stores de Zustand de la aplicación, al cliente IPC de Tauri y al kit de interfaz de shadcn. Eso hacía que las funciones no se pudieran separar del shell: nada se podía probar, reutilizar ni analizar de forma aislada. El espacio de trabajo invierte esa relación. Los paquetes contienen la lógica de cada función y declaran interfaces estrechas, llamadas puertos, para todo lo que necesitan. La aplicación implementa esos puertos sobre sus stores y su cliente de Tauri, y los inyecta. La aplicación depende de los paquetes, nunca al revés.

Los paquetes se consumen como código fuente TypeScript (su main apunta a src/index.ts). No hay paso de compilación, ni publicación, ni desajustes de versión: Vite, Vitest y tsc resuelven @oleafly/* directamente al código fuente de los paquetes mediante alias (consulta Resolución de módulos).

PaqueteQué esPuertos inyectadosDependencias destacadas
@oleafly/latexLógica pura de LaTeX y figuras: el modelo de diagramas, el serializador TikZ (modelToTikz, ida y vuelta del modelo incrustado), el constructor de documentos independientes (buildStandaloneDoc), ayudantes de nombres y bytes de figurasninguno (puro)ninguna
@oleafly/ai-coreMetadatos de presentación de proveedores y selección de modelos en el frontend, alineados con el catálogo de Rustninguno (puro)ninguna
@oleafly/latex-intelligenceTipos validados y acceso a datos del corpus de LaTeX que usa la inteligencia del editorninguno (puro)ninguna
@oleafly/registryEl registro de contribuciones: pestañas de la barra lateral, comandos de la paleta y del omnibar, conjuntos de herramientas de IA (detalles más abajo)ninguno (las contribuciones llevan su propio comportamiento)react (tipos)
@oleafly/preflightEl motor de preflight: tipado del documento, reglas de fuente, PDF y referencias, análisis ATS, preparación de exportación accesible y puntuación. pdf-extract se distribuye como la subruta @oleafly/preflight/pdf-extract para que pdf.js quede fuera de los grafos de pruebas de nodeninguno (puro)pdfjs-dist (solo la subruta)
@oleafly/pdf-to-latexLa canalización local de reconstrucción de PDF a LaTeX: agrupación de líneas, detección de encabezados y columnas, inferencia de estilos, correspondencia de símbolos, extracción de figuras, escapado y montaje del documento. El adaptador de pdf.js se distribuye como la subruta @oleafly/pdf-to-latex/pdf-adapter por el mismo motivo del grafo de pruebas de nodeninguno (puro)pdfjs-dist (solo la subruta)
@oleafly/wysiwygEl núcleo del editor visual acotado: esquema compartido, análisis y serialización de LaTeX y Markdown, y los nodos en línea y de bloque sin procesar que mantienen visible el código no compatible en lugar de descartarloninguno (puro)ninguna
@oleafly/editorEl núcleo de CodeMirror para LaTeX, Typst y Markdown: CodeMirrorEditor, el controlador de la vista del editor (insertAtCursor, gotoLine, …), lenguajes y completado, tema, plegado, encabezados fijos, linters, latex-mask, vista previa de matemáticas (KaTeX para LaTeX, Typst para Typst), ayudas de edición de Typst y conversión de matemáticas de LaTeX a Typst, el modo Visual en el sitio para LaTeX y Typst (@oleafly/editor/visual), panel de búsqueda, linters de ortografía y gramáticaEditorHost (modelo del documento y ajustes, con forma de hook), SpellHost (corrector ortográfico, Harper, diccionario), setBibKeysProvider, extraExtensions/extraKeymap@codemirror/*, @replit/codemirror-vim, @replit/codemirror-emacs, katex
@oleafly/previewEl visor de pdf.js virtualizado (PdfViewer), el controlador de páginas de SyncTeX (gotoRect, pageClickToBp), el worker de pdf.js y los polyfills de WebView (@oleafly/preview/polyfills, importados primero en main.tsx)prop onOpenLink (enlaces del navegador del sistema), setPdfLoggerpdfjs-dist
@oleafly/diagramEl compositor visual de diagramas para TikZ, Typst (fletcher) y Mermaid: lienzo de React Flow, inspector de formas, pestañas Dibujo/Código y flujo de compilar, previsualizar e insertar. Cada lenguaje en src/languages/ tiene un escritor, un lector y sus formas de inserción y de archivo, y el lienzo y el código se mantienen sincronizados en ambos sentidosDiagramHost (compilación de LaTeX, renderizado de Typst y Mermaid, E/S de archivos, inserción en el editor, corrección con IA), contexto DiagramKit (Button/Tooltip/Select/toast/tema)@xyflow/react, @codemirror/*, @oleafly/latex, @oleafly/editor
@oleafly/ai-toolsDefiniciones de herramientas de proyecto, investigación, memoria y figuras, con mutaciones que respetan las aprobacionesAiToolsHost (archivos, compilación, índice de símbolos, canalización de figuras, editor)@oleafly/latex
@oleafly/templatesLa galería de plantillas para proyectos nuevos (asistente en dos pasos, categorías, vistas previas, descargas puntuales de recursos con progreso)TemplatesHost (vistas previas, descargas de recursos, registro), TemplatesKit (Button/Tooltip), opciones de color como propsnada más allá de utilidades de interfaz

Dirección de las dependencias, aplicada de forma estricta:

Cómo dependen entre sí el shell de la aplicación y los paquetesEl shell de la aplicación en src/ contiene los stores de Zustand, el kit de interfaz, el cliente de Tauri y los adaptadores de puertos. Los adaptadores implementan los puertos que declaran los paquetes de funciones @oleafly, como EditorHost, y la aplicación los inyecta. Los paquetes de funciones pueden depender de paquetes hoja puros como latex, ai-core y registry. El cliente de Tauri habla con el backend de Rust mediante IPC de Tauri. Los paquetes nunca importan la aplicación, sus stores ni las API de Tauri.Shell de la aplicación, src/Paquetes @oleafly/*StoresZustandAdaptadoresImplementan puertosKit de interfazshadcnCliente de Taurisrc/lib/tauri.tsPaquetes de funcionesDeclaran puertos como EditorHostPaquetes hojalatex, ai-core, registryBackend de Rustsrc-tauri, crates/oleafly-agentInyectaTauri IPCLos paquetes nunca importan la aplicación, sus stores ni las API de Tauri.Cómo dependen entre sí el shell de la aplicación y los paquetesEl shell de la aplicación en src/ contiene los stores de Zustand, el kit de interfaz, el cliente de Tauri y los adaptadores de puertos. Los adaptadores implementan los puertos que declaran los paquetes de funciones @oleafly, como EditorHost, y la aplicación los inyecta. Los paquetes de funciones pueden depender de paquetes hoja puros como latex, ai-core y registry. El cliente de Tauri habla con el backend de Rust mediante IPC de Tauri. Los paquetes nunca importan la aplicación, sus stores ni las API de Tauri.Shell de la aplicación, src/Paquetes @oleafly/*StoresKit de interfazCliente de TauriAdaptadoresBackend de Rustsrc-tauri, cratesPaquetes de funcionesDeclaran los puertosPaquetes hojalatex, ai-core, registryTauri IPCInyectaLos paquetes nunca importan la aplicación,sus stores ni las API de Tauri.
La aplicación depende de los paquetes e inyecta adaptadores para los puertos que declaran. Los paquetes nunca importan la aplicación.

Un paquete nunca debe importar desde src/ (@/…), un store de Zustand, @tauri-apps/* ni los componentes de interfaz de la aplicación. La comprobación de pureza forma parte de la higiene de extracción:

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

Los paquetes declaran interfaces. La aplicación las implementa una vez, normalmente como un objeto adaptador a nivel de módulo que encapsula sus stores. Hay cuatro formas recurrentes:

Puertos host: una interface XHost por paquete que cubre los servicios que necesita. La aplicación construye el adaptador con lecturas imperativas de los 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);

Mantener dentro del adaptador lo que solo concierne a la aplicación también mantiene las dependencias fuera de los paquetes. Las credenciales y las peticiones de los proveedores se quedan en Rust, así que los paquetes de frontend solo reciben el estado y los resultados que exigen sus interfaces.

Kits de interfaz: un contexto de React (o una prop) con componentes de tipo estructural: Button: ComponentType<{ variant?: …; onClick?: () => void }>. La aplicación pasa sus componentes de shadcn sin cambios porque el tipo estructural más pequeño es compatible. Los kits también incluyen toast y un hook useThemeMode().

Puertos con forma de hook: para paquetes que deben suscribirse al estado de la aplicación. El EditorHost del editor tiene miembros hook (useActivePath(), useSettings()) y gemelos imperativos (getActivePath()) para los listeners de eventos. Reglas: el objeto host debe estar a nivel de módulo (identidad estable) y sus miembros use* deben llamar a los hooks de forma incondicional.

Singletons de módulo: para el pegamento transversal, setPdfLogger(fn), setSpellHost(host) y setBibKeysProvider(fn) tienen valores por defecto que no hacen nada y el shim de la aplicación los instala como efecto secundario de la importación.

Cada extracción dejó un archivo fino en la antigua ruta @/… que reexporta (o envuelve) el paquete, así que los consumidores y las rutas existentes de vi.mock(...) nunca tuvieron que cambiar. Ejemplos: src/components/editor/cm/controller.ts (reexportación), src/components/pdf/PdfViewer.tsx (envoltorio que inyecta el abridor de enlaces del plugin shell) y src/lib/ai-tools.ts (adaptador y firmas originales de las fábricas). El código nuevo puede importar @oleafly/* directamente. Los shims existen por compatibilidad, no como la vía preferida.

@oleafly/registry es la forma en que las funciones se conectan al shell de la aplicación sin que el shell las conozca. Hay tres tipos de contribución:

  • Pestañas de la barra lateral (registerRailTab): icono, sección (explore / review / assist, con separadores entre las secciones que no están vacías), visibilidad opcional con when(ctx), hook opcional useBadge() (por ejemplo, el número de cambios de Git) y el componente panel de la barra lateral.
  • Comandos (registerCommand): se ofrecen en la paleta de comandos (Cmd+K, agrupados por group) y/o en el omnibar (Cmd+P) mediante surfaces. Las etiquetas y los iconos pueden ser funciones del contexto (por ejemplo, “Cambiar al tema claro”).
  • Conjuntos de herramientas de IA (registerAiToolset): un modo de chat ("chat", "figure") asociado a una fábrica de herramientas. El panel de chat busca las herramientas del modo activo en lugar de tener unas fijas en el código.

Cada superficie que renderiza construye AppContext ({ projectId, projectKind, theme }) a partir de sus propias suscripciones, así que las condiciones when/label siguen siendo reactivas.

El registro es estático: src/contributions/ registra todo lo incorporado (pestañas, comandos, conjuntos de herramientas) y main.tsx llama una vez a registerContributions() antes de montar el shell. Rail, Sidebar, CommandPalette, SearchOmnibar y ChatPanel solo renderizan lo que está registrado.

Para añadir la pestaña o el comando de una función, regístralo. Sin tocar el 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"),
});

Es una API interna, no un SDK de plugins: sus formas pueden cambiar libremente y nada se carga de forma dinámica. Quedan pendientes hasta que exista un consumidor real: formatos del menú de exportación, esquemas de ajustes y comportamientos por tipo de proyecto como contribuciones.

Como los paquetes se consumen como código fuente, el mismo alias se declara en tres sitios, y los tres deben mantenerse sincronizados cuando se añade un paquete:

  1. tsconfig.json → compilerOptions.paths. Las subrutas como @oleafly/preflight/pdf-extract necesitan su propia entrada explícita.
  2. vite.config.ts → resolve.alias. Los alias de texto coinciden por prefijo, así que las subrutas se resuelven sin otra entrada.
  3. vitest.config.ts → el mismo resolve.alias, y además test.include debe cubrir packages/**/*.test.ts. Si no, las pruebas movidas desaparecen de la ejecución sin avisar.

Además: el package.json raíz depende de cada paquete mediante "workspace:*", y src/styles/globals.css tiene @source "../../packages" para que Tailwind v4 busque nombres de clases en el código fuente de los paquetes.

Las importaciones exclusivas de Vite (workers con ?worker&url, importaciones de CSS) funcionan sin cambios desde el código fuente de los paquetes porque los alias apuntan al código fuente. Mantén todo lo que arrastre pdf.js fuera de los barrels que importan las pruebas en entorno node: por eso pdf-extract y el visor del paquete de vista previa solo son accesibles donde existe el DOM.

  • Las pruebas viven junto al código fuente (packages/*/src/*.test.ts) y se ejecutan en el mismo pnpm test que las pruebas de la aplicación. Vigila el número de pruebas cuando muevas archivos.
  • Las dependencias se declaran de forma laxa ("react": "*"). pnpm las deduplica contra la raíz, donde están los mínimos de versión reales. No declares dependencias que el paquete no importa.
  • Comprobaciones para cualquier cambio en un paquete: pnpm exec tsc --noEmit, pnpm build, pnpm test (con el mismo número de pruebas) y el grep de pureza de arriba.
  • Extraer algo nuevo: mueve los archivos con git mv (el historial los sigue), define los puertos, deja shims en las rutas antiguas, conecta los tres alias y ejecuta las comprobaciones. No extraigas una función mientras sigues iterando sobre ella.
  • Licencia: todos los paquetes son AGPL-3.0-or-later, igual que la aplicación.

src/ conserva todo lo que es realmente la aplicación de frontend: los stores de Zustand, el cliente IPC de Tauri (src/lib/tauri.ts), el kit de interfaz de shadcn, los componentes de diseño y del shell, los adaptadores de puertos y los shims, src/contributions/, y el pegamento de funciones que por naturaleza está acoplado a los stores (la interfaz del panel de preflight, el plugin de edición con IA en línea y la inteligencia de código o de hover inyectada en el editor como extraExtensions). Los comandos de Tauri y la integración con el sistema operativo se quedan en src-tauri/. Los clientes de proveedores, el streaming y la orquestación de agentes pertenecen a crates/oleafly-agent/, para que los secretos y las peticiones a los proveedores queden fuera del webview.

Vista previa de la imagen

No se pudieron cargar las respuestas de la IA

Inténtalo de nuevo o busca en la documentación.