Aller au contenu
Oleaflyv0.4.7

Comment Oleafly répartit l'application de bureau entre le workspace frontend, les services Rust et les sidecars épinglés.

Le frontend d’Oleafly est un workspace pnpm : le shell de l’application vit à la racine du dépôt (src/), et le code frontend réutilisable vit dans packages/* sous forme de douze packages ciblés. Le backend Tauri vit dans src-tauri/, tandis que le code réutilisable pour les fournisseurs d’IA et l’agent vit dans crates/oleafly-agent/. Consultez le schéma d’architecture du README (s’ouvre dans un nouvel onglet) pour la répartition frontend/Rust/sidecar et Développement pour les commandes du quotidien.

Ce document couvre : ce qu’est chaque package, les règles d’inversion des dépendances qui les gardent purs, le registre de contributions qui câble les fonctionnalités dans le shell, le fonctionnement de la résolution des modules, et comment ajouter ou extraire un package.

Chaque fonctionnalité accédait autrefois directement aux stores Zustand de l’application, au client IPC Tauri et à la bibliothèque UI shadcn. Les fonctionnalités étaient ainsi indissociables du shell : rien ne pouvait être testé, réutilisé ou analysé isolément. Le workspace inverse cette logique. Les packages portent la logique des fonctionnalités et déclarent des interfaces étroites, appelées ports, pour tout ce dont ils ont besoin. L’application implémente ces ports au-dessus de ses stores et de son client Tauri, puis les injecte. L’application dépend des packages, jamais l’inverse.

Les packages sont consommés comme code source TypeScript (leur main pointe vers src/index.ts). Il n’y a ni étape de build, ni publication, ni décalage de version : Vite, Vitest et tsc résolvent tous @oleafly/* directement vers les sources des packages via des alias (voir Résolution des modules).

PackageCe que c’estPorts injectésDépendances notables
@oleafly/latexLogique LaTeX/figures pure : le modèle de diagrammes, le sérialiseur TikZ (modelToTikz, aller-retour du modèle embarqué), le constructeur de documents autonomes (buildStandaloneDoc), les utilitaires de noms et d’octets des figuresaucun (pur)aucune
@oleafly/ai-coreMétadonnées d’affichage des fournisseurs et sélection des modèles côté frontend, maintenues en parité avec le catalogue Rustaucun (pur)aucune
@oleafly/latex-intelligenceTypes validés et accès aux données pour le corpus LaTeX utilisé par l’intelligence de l’éditeuraucun (pur)aucune
@oleafly/registryLe registre de contributions : onglets du rail, commandes de la palette et de l’omnibar, ensembles d’outils IA (détails ci-dessous)aucun (les contributions portent leur propre comportement)react (types)
@oleafly/preflightLe moteur de prévérification : typage des documents, règles source/PDF/références, analyse ATS, préparation des exports accessibles, notation. pdf-extract est livré via le sous-chemin @oleafly/preflight/pdf-extract pour que pdf.js reste hors des graphes de tests nodeaucun (pur)pdfjs-dist (sous-chemin uniquement)
@oleafly/pdf-to-latexLe pipeline local de reconstruction de PDF vers LaTeX : regroupement des lignes, détection des titres et des colonnes, inférence de style, mise en correspondance des symboles, extraction des figures, échappement et assemblage du document. L’adaptateur pdf.js est livré via le sous-chemin @oleafly/pdf-to-latex/pdf-adapter pour la même raison de graphe de tests nodeaucun (pur)pdfjs-dist (sous-chemin uniquement)
@oleafly/wysiwygLe cœur de l’éditeur visuel contraint : schéma partagé, analyse et sérialisation LaTeX et Markdown, et nœuds bruts inline/bloc qui gardent la source non prise en charge visible au lieu de la supprimeraucun (pur)aucune
@oleafly/editorLe cœur CodeMirror pour LaTeX, Typst et Markdown : CodeMirrorEditor, le contrôleur de vue de l’éditeur (insertAtCursor, gotoLine, …), langages + complétions, thème, pliage, titres épinglés, linters, latex-mask, aperçu mathématique (KaTeX pour LaTeX, Typst pour Typst), aides d’édition Typst et conversion des maths LaTeX vers Typst, mode Visuel intégré pour LaTeX et Typst (@oleafly/editor/visual), panneau de recherche, linters d’orthographe et de grammaireEditorHost (modèle de document + réglages, en forme de hooks), SpellHost (correcteur orthographique/Harper/dictionnaire), setBibKeysProvider, extraExtensions/extraKeymap@codemirror/*, @replit/codemirror-vim, @replit/codemirror-emacs, katex
@oleafly/previewLa visionneuse pdf.js virtualisée (PdfViewer), le contrôleur de pages SyncTeX (gotoRect, pageClickToBp), le worker pdf.js et les polyfills WebView (@oleafly/preview/polyfills, importés en premier dans main.tsx)prop onOpenLink (liens vers le navigateur système), setPdfLoggerpdfjs-dist
@oleafly/diagramLe compositeur visuel de diagrammes pour TikZ, Typst (fletcher) et Mermaid : canevas React Flow, inspecteur de formes, onglets Dessin/Code, flux compiler, prévisualiser puis insérer. Chaque langage de src/languages/ possède un writer, un reader ainsi que ses formes d’insertion et de fichier, et le canevas et le code restent synchronisés dans les deux sensDiagramHost (compilation LaTeX, rendu Typst et Mermaid, E/S de fichiers, insertion dans l’éditeur, correction IA), contexte DiagramKit (Button/Tooltip/Select/toast/theme)@xyflow/react, @codemirror/*, @oleafly/latex, @oleafly/editor
@oleafly/ai-toolsDéfinitions des outils de projet, de recherche, de mémoire et de figures, avec des mutations conscientes des approbationsAiToolsHost (fichiers, compilation, index des symboles, pipeline de figures, éditeur)@oleafly/latex
@oleafly/templatesLa galerie de modèles de nouveaux projets (assistant en deux étapes, catégories, prévisualisations, téléchargements uniques des ressources avec progression)TemplatesHost (prévisualisations, téléchargements des ressources, journalisation), TemplatesKit (Button/Tooltip), options de couleur en propsrien au-delà des utilitaires UI

Sens des dépendances, strictement appliqué :

Comment le shell de l’application et les paquets dépendent les uns des autresLe shell de l’application dans src/ contient les stores Zustand, le kit d’interface, le client Tauri et les adaptateurs de ports. Les adaptateurs implémentent les ports que déclarent les paquets de fonctions @oleafly, comme EditorHost, et l’application les injecte. Les paquets de fonctions peuvent dépendre de paquets feuilles purs comme latex, ai-core et registry. Le client Tauri dialogue avec le backend Rust via l’IPC de Tauri. Les paquets n’importent jamais l’application, ses stores ni les API Tauri.Shell de l’application, src/Paquets @oleafly/*StoresZustandAdaptateursImplémentent les portsKit d’interfaceshadcnClient Taurisrc/lib/tauri.tsPaquets de fonctionsDéclarent des ports comme EditorHostPaquets feuilleslatex, ai-core, registryBackend Rustsrc-tauri, crates/oleafly-agentInjecteTauri IPCLes paquets n’importent jamais l’application, ses stores ni les API Tauri.Comment le shell de l’application et les paquets dépendent les uns des autresLe shell de l’application dans src/ contient les stores Zustand, le kit d’interface, le client Tauri et les adaptateurs de ports. Les adaptateurs implémentent les ports que déclarent les paquets de fonctions @oleafly, comme EditorHost, et l’application les injecte. Les paquets de fonctions peuvent dépendre de paquets feuilles purs comme latex, ai-core et registry. Le client Tauri dialogue avec le backend Rust via l’IPC de Tauri. Les paquets n’importent jamais l’application, ses stores ni les API Tauri.Shell de l’application, src/Paquets @oleafly/*StoresKit d’interfaceClient TauriAdaptateursBackend Rustsrc-tauri, cratesPaquets de fonctionsDéclarent les portsPaquets feuilleslatex, ai-core, registryTauri IPCInjecteLes paquets n’importent jamais l’application,ses stores ni les API Tauri.
L’application dépend des paquets et injecte des adaptateurs pour les ports qu’ils déclarent. Les paquets n’importent jamais l’application.

Un package ne doit jamais importer depuis src/ (@/…), un store Zustand, @tauri-apps/* ou les composants UI de l’application. La vérification de pureté fait partie de l’hygiène d’extraction :

Fenêtre de terminal
grep -rn 'from "@/\|@tauri\|zustand' packages/*/src # must return nothing

Les packages déclarent des interfaces. L’application les implémente une fois, généralement sous forme d’un objet adaptateur au niveau du module qui capture ses stores. Quatre formes récurrentes :

Ports d’hôte : une interface XHost par package couvrant les services dont il a besoin. L’application construit l’adaptateur avec des lectures impératives des 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);

Regrouper ce qui est propre à l’application dans l’adaptateur maintient aussi les dépendances hors des packages. Les identifiants et les requêtes des fournisseurs restent en Rust, donc les packages frontend ne reçoivent que l’état et les résultats qu’exigent leurs interfaces.

Kits UI : un contexte React (ou une prop) de composants typés structurellement : Button: ComponentType<{ variant?: …; onClick?: () => void }>. L’application transmet ses composants shadcn tels quels, car le type structurel plus petit est compatible. Les kits portent aussi toast et un hook useThemeMode().

Ports en forme de hooks : pour les packages qui doivent s’abonner à l’état de l’application. L’EditorHost de l’éditeur possède des membres en forme de hooks (useActivePath(), useSettings()) ainsi que des jumeaux impératifs (getActivePath()) pour les écouteurs d’événements. Règles : l’objet hôte doit être au niveau du module (identité stable) et ses membres use* doivent appeler des hooks inconditionnellement.

Singletons de module : pour la glu transversale, setPdfLogger(fn), setSpellHost(host) et setBibKeysProvider(fn) ont des valeurs par défaut no-op et sont installés par le shim de l’application comme effet de bord d’import.

Chaque extraction a laissé un fichier mince à l’ancien chemin @/… qui réexporte (ou encapsule) le package, afin que les consommateurs et les chemins vi.mock(...) existants ne changent jamais. Exemples : src/components/editor/cm/controller.ts (réexportation), src/components/pdf/PdfViewer.tsx (wrapper qui injecte l’ouvreur de liens du plugin shell), src/lib/ai-tools.ts (adaptateur + signatures d’origine de la fabrique). Le nouveau code peut importer @oleafly/* directement. Les shims existent pour la compatibilité, pas comme chemin privilégié.

@oleafly/registry est la façon dont les fonctionnalités se branchent sur le shell de l’application sans que le shell les connaisse. Trois types de contributions :

  • Onglets du rail (registerRailTab) : icône, section (explore / review / assist, séparateurs tracés entre les sections non vides), visibilité when(ctx) facultative, hook useBadge() facultatif (par ex. le nombre de modifications Git), et le composant panel de la barre latérale.
  • Commandes (registerCommand) : servies à la palette de commandes (Cmd+K, groupées par group) et/ou à l’omnibar (Cmd+P) via surfaces. Les libellés et les icônes peuvent être des fonctions du contexte (par ex. « Passer au thème clair »).
  • Ensembles d’outils IA (registerAiToolset) : un mode de discussion ("chat", "figure") associé à une fabrique d’ensemble d’outils. Le panneau de discussion recherche l’ensemble d’outils du mode actif au lieu d’en figer un dans le code.

AppContext ({ projectId, projectKind, theme }) est construit par chaque surface de rendu à partir de ses propres abonnements, pour que les conditions when/label restent réactives.

L’enregistrement est statique : src/contributions/ enregistre tous les éléments intégrés (onglets, commandes, ensembles d’outils) et main.tsx appelle registerContributions() une seule fois avant le montage du shell. Rail, Sidebar, CommandPalette, SearchOmnibar et ChatPanel n’affichent que ce qui est enregistré.

Pour ajouter l’onglet ou la commande d’une fonctionnalité, enregistrez-le. Aucune modification du 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"),
});

C’est une API interne, pas un SDK de plugins : les formes peuvent évoluer librement, et rien n’est chargé dynamiquement. Reportés jusqu’à l’apparition d’un vrai consommateur : les formats du menu d’export, les schémas des réglages et les comportements par type de projet en tant que contributions.

Comme les packages sont consommés comme code source, le même alias est déclaré à trois endroits, qui doivent tous rester synchronisés lors de l’ajout d’un package :

  1. tsconfig.json → compilerOptions.paths. Les sous-chemins comme @oleafly/preflight/pdf-extract nécessitent leur propre entrée explicite.
  2. vite.config.ts → resolve.alias. Les alias de chaînes correspondent par préfixe, donc les sous-chemins se résolvent sans entrée supplémentaire.
  3. vitest.config.ts → le même resolve.alias, et test.include doit couvrir packages/**/*.test.ts. Sinon, les tests déplacés disparaissent silencieusement de l’exécution.

À noter aussi : le package.json racine dépend de chaque package via "workspace:*", et src/styles/globals.css contient @source "../../packages" pour que Tailwind v4 analyse les sources des packages à la recherche de noms de classes.

Les imports réservés à Vite (workers ?worker&url, imports CSS) fonctionnent sans changement depuis les sources des packages parce que les alias pointent vers la source. Gardez tout ce qui tire pdf.js hors des barrels que des tests en environnement node importent : c’est pourquoi pdf-extract et la visionneuse du package preview ne sont accessibles que là où le DOM existe.

  • Tests : ils vivent à côté de la source (packages/*/src/*.test.ts) et s’exécutent dans le même pnpm test que les tests de l’application. Surveillez le nombre de tests en déplaçant des fichiers.
  • Dépendances : elles sont déclarées de façon lâche ("react": "*"). pnpm déduplique par rapport à la racine, où se trouvent les vrais planchers de versions. Ne déclarez pas de dépendances que le package n’importe pas.
  • Critères de validation pour tout changement de package : pnpm exec tsc --noEmit, pnpm build, pnpm test (nombre inchangé) et le grep de pureté ci-dessus.
  • Extraire quelque chose de nouveau : déplacez les fichiers avec git mv (l’historique suit), définissez les ports, laissez des shims aux anciens chemins, câblez les trois alias, puis exécutez les critères de validation. N’extrayez pas une fonctionnalité sur laquelle vous itérez activement.
  • Licence : chaque package est AGPL-3.0-or-later, comme l’application.

src/ garde tout ce qui constitue réellement l’application frontend : les stores Zustand, le client IPC Tauri (src/lib/tauri.ts), la bibliothèque UI shadcn, les composants de disposition/shell, les adaptateurs et shims des ports, src/contributions/, et la glu de fonctionnalités couplée aux stores par nature (l’interface du panneau de prévérification, le plugin de modifications IA en ligne, et l’intelligence de code ou de survol injectée dans l’éditeur comme extraExtensions). Les commandes Tauri et l’intégration au système d’exploitation restent dans src-tauri/. Les clients des fournisseurs, le streaming et l’orchestration de l’agent appartiennent à crates/oleafly-agent/ afin que les secrets et les requêtes aux fournisseurs restent hors du webview.

Aperçu de l’image

Impossible de charger les réponses de l’IA

Réessayez ou cherchez dans la documentation.