Zum Inhalt springen
Oleaflyv0.4.7

Wie Oleafly die Desktop-App zwischen Frontend-Workspace, Rust-Diensten und gepinnten Sidecars aufteilt.

Oleaflys Frontend ist ein pnpm-Workspace: Die App-Shell liegt im Wurzelverzeichnis des Repositorys (src/), wiederverwendbarer Frontend-Code liegt als zwölf fokussierte Pakete in packages/*. Das Tauri-Backend liegt in src-tauri/, wiederverwendbarer KI-Provider- und Agent-Code liegt in crates/oleafly-agent/. Das Architekturdiagramm im README (öffnet in einem neuen Tab) zeigt das Bild aus Frontend/Rust/Sidecar; unter Entwicklung finden Sie die Befehle für den Alltag.

Dieses Dokument behandelt: Was jedes Paket ist, die Regeln der Abhängigkeitsinversion, die die Pakete rein halten, die Contribution-Registry, die Features mit der Shell verdrahtet, wie die Modulauflösung funktioniert und wie man ein Paket hinzufügt oder extrahiert.

Früher griff jedes Feature direkt auf die Zustand-Stores der App, den Tauri-IPC-Client und das shadcn-UI-Kit zu. Damit waren Features von der Shell unzertrennlich: Nichts ließ sich isoliert testen, wiederverwenden oder durchdenken. Der Workspace kehrt das um. Die Pakete halten die Feature-Logik und deklarieren schmale Schnittstellen, sogenannte Ports, für alles, was sie brauchen. Die App implementiert diese Ports über ihre Stores und den Tauri-Client und injiziert sie. Die App hängt von den Paketen ab, niemals umgekehrt.

Pakete werden als TypeScript-Quelltext konsumiert (ihr main zeigt auf src/index.ts). Es gibt keinen Build-Schritt, kein Publishing und keinen Versionsdrift: Vite, Vitest und tsc lösen @oleafly/* per Aliase direkt zu den Paketquellen auf (siehe Modulauflösung).

PaketWas es istInjizierte PortsWichtige Abhängigkeiten
@oleafly/latexReine Logik für LaTeX und Abbildungen: das Diagrammmodell, der TikZ-Serialisierer (modelToTikz, Round-Trip des eingebetteten Modells), der Builder für Standalone-Dokumente (buildStandaloneDoc), Helfer für Abbildungsnamen und -byteskeine (rein)keine
@oleafly/ai-coreAnzeigemetadaten der Provider und die Modellauswahl im Frontend, auf dem Stand des Rust-Katalogs gehaltenkeine (rein)keine
@oleafly/latex-intelligenceValidierte Typen und Datenzugriff für das LaTeX-Korpus, das die Editor-Intelligenz nutztkeine (rein)keine
@oleafly/registryDie Contribution-Registry: Rail-Tabs, Palette-/Omnibar-Befehle, KI-Toolsets (Details unten)keine (Contributions bringen ihr eigenes Verhalten mit)react (Typen)
@oleafly/preflightDie Preflight-Engine: Dokumenttypisierung, Regeln für Quelle/PDF/Referenzen, ATS-Parsing, Vorbereitung barrierefreier Exporte, Scoring. pdf-extract wird als Subpath @oleafly/preflight/pdf-extract ausgeliefert, damit pdf.js aus den Node-Testgraphen herausbleibtkeine (rein)pdfjs-dist (nur Subpath)
@oleafly/pdf-to-latexDie lokale Rekonstruktions-Pipeline von PDF zu LaTeX: Zeilengruppierung, Erkennung von Überschriften und Spalten, Stilinferenz, Symbol-Mapping, Abbildungsextraktion, Escaping und Dokumentzusammenbau. Der pdf.js-Adapter wird aus demselben Grund (Node-Testgraph) als Subpath @oleafly/pdf-to-latex/pdf-adapter ausgeliefertkeine (rein)pdfjs-dist (nur Subpath)
@oleafly/wysiwygDer Kern des beschränkten visuellen Editors: gemeinsames Schema, Parsen und Serialisieren von LaTeX und Markdown sowie die rohen Inline-/Block-Knoten, die nicht unterstützten Quelltext sichtbar halten, statt ihn zu verwerfenkeine (rein)keine
@oleafly/editorDer CodeMirror-Kern für LaTeX, Typst und Markdown: CodeMirrorEditor, der Controller der Editor-Ansicht (insertAtCursor, gotoLine, …), Sprachen und Vervollständigungen, Theme, Folding, Sticky-Überschriften, Linter, latex-mask, Mathe-Vorschau (KaTeX für LaTeX, Typst für Typst), Typst-Bearbeitungshilfen und die Umwandlung von LaTeX-Mathe nach Typst, der In-Place-Visual-Modus für LaTeX und Typst (@oleafly/editor/visual), Suchpanel, Rechtschreib-/Grammatik-LinterEditorHost (Dokumentmodell + Einstellungen, hook-förmig), SpellHost (Rechtschreibprüfung/Harper/Wörterbuch), setBibKeysProvider, extraExtensions/extraKeymap@codemirror/*, @replit/codemirror-vim, @replit/codemirror-emacs, katex
@oleafly/previewDer virtualisierte pdf.js-Viewer (PdfViewer), der SyncTeX-Seiten-Controller (gotoRect, pageClickToBp), der pdf.js-Worker und die WebView-Polyfills (@oleafly/preview/polyfills, in main.tsx zuerst importiert)onOpenLink-Prop (Links an den Systembrowser), setPdfLoggerpdfjs-dist
@oleafly/diagramDer visuelle Diagramm-Composer für TikZ, Typst (fletcher) und Mermaid: React-Flow-Canvas, Form-Inspektor, Tabs „Zeichnen“/„Code“, Ablauf Kompilieren-Vorschau-Einfügen. Jede Sprache in src/languages/ hat einen Writer, einen Reader sowie ihr Einfüge- und ihr Dateiformular, und Canvas und Code bleiben in beide Richtungen im TaktDiagramHost (LaTeX-Kompilierung, Typst- und Mermaid-Rendering, Datei-IO, Editor-Einfügen, KI-Fix), DiagramKit-Kontext (Button/Tooltip/Select/toast/theme)@xyflow/react, @codemirror/*, @oleafly/latex, @oleafly/editor
@oleafly/ai-toolsWerkzeugdefinitionen für Projekt, Recherche, Gedächtnis und Abbildungen mit genehmigungsbewussten MutationenAiToolsHost (Dateien, Kompilieren, Symbolindex, Abbildungs-Pipeline, Editor)@oleafly/latex
@oleafly/templatesDie Vorlagengalerie für neue Projekte (zweistufiger Assistent, Kategorien, Vorschauen, einmalige Asset-Downloads mit Fortschritt)TemplatesHost (Vorschauen, Asset-Downloads, Logging), TemplatesKit (Button/Tooltip), Farboptionen als Propskeine außer UI-Utilities

Abhängigkeitsrichtung, strikt erzwungen:

Wie App-Shell und Pakete voneinander abhängenDie App-Shell in src/ enthält die Zustand-Stores, das UI-Kit, den Tauri-Client und die Port-Adapter. Die Adapter implementieren die Ports, die die @oleafly-Funktionspakete deklarieren, etwa EditorHost, und die App injiziert sie. Funktionspakete dürfen von reinen Blattpaketen wie latex, ai-core und registry abhängen. Der Tauri-Client spricht über Tauri-IPC mit dem Rust-Backend. Pakete importieren niemals die App, ihre Stores oder Tauri-APIs.App-Shell, src/@oleafly/*-PaketeStoresZustandAdapterImplementieren PortsUI-KitshadcnTauri-Clientsrc/lib/tauri.tsFunktionspaketeDeklarieren Ports wie EditorHostBlattpaketelatex, ai-core, registryRust-Backendsrc-tauri, crates/oleafly-agentInjiziertTauri IPCPakete importieren niemals die App, ihre Stores oder Tauri-APIs.Wie App-Shell und Pakete voneinander abhängenDie App-Shell in src/ enthält die Zustand-Stores, das UI-Kit, den Tauri-Client und die Port-Adapter. Die Adapter implementieren die Ports, die die @oleafly-Funktionspakete deklarieren, etwa EditorHost, und die App injiziert sie. Funktionspakete dürfen von reinen Blattpaketen wie latex, ai-core und registry abhängen. Der Tauri-Client spricht über Tauri-IPC mit dem Rust-Backend. Pakete importieren niemals die App, ihre Stores oder Tauri-APIs.App-Shell, src/@oleafly/*-PaketeStoresUI-KitTauri-ClientAdapterRust-Backendsrc-tauri, cratesFunktionspaketeDeklarieren die PortsBlattpaketelatex, ai-core, registryTauri IPCInjiziertPakete importieren niemals die App,ihre Stores oder Tauri-APIs.
Die App hängt von den Paketen ab und injiziert Adapter für die Ports, die sie deklarieren. Pakete importieren niemals die App.

Ein Paket darf niemals aus src/ (@/…) importieren, weder aus einem Zustand-Store, aus @tauri-apps/* noch aus den UI-Komponenten der App. Der Reinheitscheck ist Teil der Extraktionshygiene:

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

Pakete deklarieren Schnittstellen. Die App implementiert sie einmal, üblicherweise als Adapter-Objekt auf Modulebene, das die Stores per Closure einschließt. Vier wiederkehrende Formen:

Host-Ports: ein interface XHost pro Paket, das die benötigten Dienste abdeckt. Die App baut den Adapter mit imperativen Store-Lesezugriffen:

// 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);

App-Anliegen im Adapter zu halten hält auch Abhängigkeiten aus den Paketen heraus. Provider-Zugangsdaten und -Anfragen bleiben in Rust, sodass Frontend-Pakete nur den Zustand und die Ergebnisse erhalten, die ihre Schnittstellen verlangen.

UI-Kits: ein React-Kontext (oder eine Prop) mit strukturell typisierten Komponenten: Button: ComponentType<{ variant?: …; onClick?: () => void }>. Die App reicht ihre shadcn-Komponenten unverändert durch, weil der kleinere strukturelle Typ kompatibel ist. Kits tragen außerdem toast und einen useThemeMode()-Hook.

Hook-förmige Ports: für Pakete, die App-Zustand abonnieren müssen. Der EditorHost des Editors hat Hook-Mitglieder (useActivePath(), useSettings()) plus imperative Zwillinge (getActivePath()) für Event-Listener. Regeln: Das Host-Objekt muss auf Modulebene liegen (stabile Identität), und seine use*-Mitglieder müssen Hooks bedingungslos aufrufen.

Modul-Singletons: für übergreifenden Klebstoff; setPdfLogger(fn), setSpellHost(host) und setBibKeysProvider(fn) haben No-op-Defaults und werden vom App-Shim als Import-Nebenwirkung installiert.

Jede Extraktion hat am alten @/…-Pfad eine dünne Datei hinterlassen, die das Paket re-exportiert (oder wrappt), damit Konsumenten und bestehende vi.mock(...)-Pfade nie umgebaut werden mussten. Beispiele: src/components/editor/cm/controller.ts (Re-Export), src/components/pdf/PdfViewer.tsx (Wrapper, der den Link-Öffner des Shell-Plugins injiziert), src/lib/ai-tools.ts (Adapter + ursprüngliche Factory-Signaturen). Neuer Code darf @oleafly/* direkt importieren. Shims existieren der Kompatibilität wegen, nicht als bevorzugter Pfad.

@oleafly/registry ist der Weg, auf dem sich Features in die App-Shell einklinken, ohne dass die Shell sie kennen muss. Drei Arten von Contributions:

  • Rail-Tabs (registerRailTab): Icon, Section (explore / review / assist, Trennlinien werden zwischen nicht-leeren Sections gezogen), optionale Sichtbarkeit über when(ctx), optionaler useBadge()-Hook (z. B. die Anzahl der Git-Änderungen) und die panel-Komponente der Sidebar.
  • Befehle (registerCommand): werden über surfaces der Befehlspalette (Cmd+K, nach group gruppiert) und/oder der Omnibar (Cmd+P) angeboten. Labels und Icons können Funktionen des Kontexts sein (z. B. „Zum hellen Design wechseln“).
  • KI-Toolsets (registerAiToolset): ein Chatmodus ("chat", "figure"), der auf eine Toolset-Factory abgebildet wird. Das Chat-Panel schlägt das Toolset des aktiven Modus nach, statt eines festzuverdrahten.

AppContext ({ projectId, projectKind, theme }) wird von jeder rendernden Oberfläche aus ihren eigenen Abonnements gebaut, sodass when/label-Bedingungen reaktiv bleiben.

Die Registrierung ist statisch: src/contributions/ registriert alles Eingebaute (Tabs, Befehle, Toolsets), und main.tsx ruft registerContributions() einmal auf, bevor die Shell gemountet wird. Rail, Sidebar, CommandPalette, SearchOmnibar und ChatPanel rendern nur das Registrierte.

Um das Tab oder den Befehl eines Features hinzuzufügen, registrieren Sie es. Keine Bearbeitung der 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"),
});

Das ist eine interne API, kein Plugin-SDK: Formen dürfen sich frei ändern, und nichts wird dynamisch geladen. Zurückgestellt, bis es einen echten Konsumenten gibt: Exportmenü-Formate, Einstellungs-Schemas und Projektart-Verhalten als Contributions.

Weil Pakete als Quelltext konsumiert werden, wird derselbe Alias an drei Stellen deklariert, und alle drei müssen synchron bleiben, wenn ein Paket hinzukommt:

  1. tsconfig.json → compilerOptions.paths. Subpaths wie @oleafly/preflight/pdf-extract brauchen einen eigenen expliziten Eintrag.
  2. vite.config.ts → resolve.alias. String-Aliase matchen nach Präfix, sodass Subpaths ohne weiteren Eintrag auflösen.
  3. vitest.config.ts → derselbe resolve.alias, plus test.include muss packages/**/*.test.ts abdecken. Andernfalls fallen verschobene Tests still aus dem Lauf heraus.

Außerdem: Das Wurzel-package.json hängt per "workspace:*" von jedem Paket ab, und src/styles/globals.css enthält @source "../../packages", damit Tailwind v4 die Paketquellen nach Klassennamen scannt.

Nur in Vite existierende Importe (?worker&url-Worker, CSS-Importe) funktionieren aus Paketquellen unverändert, weil die Aliase auf den Quelltext zeigen. Halten Sie alles, was pdf.js mit sich zieht, aus Barrels heraus, die Node-Umgebungstests importieren: Deshalb sind pdf-extract und der Viewer des Preview-Pakets nur dort erreichbar, wo das DOM existiert.

  • Tests liegen neben dem Quelltext (packages/*/src/*.test.ts) und laufen im selben pnpm test wie die App-Tests. Achten Sie beim Verschieben von Dateien auf die Anzahl der Tests.
  • Abhängigkeiten werden locker deklariert ("react": "*"). pnpm dedupliziert gegen die Wurzel, wo die echten Versionsuntergrenzen liegen. Deklarieren Sie keine Abhängigkeiten, die das Paket nicht importiert.
  • Gates für jede Paketänderung: pnpm exec tsc --noEmit, pnpm build, pnpm test (unveränderte Anzahl) und der Reinheits-grep von oben.
  • Etwas Neues extrahieren: Verschieben Sie Dateien mit git mv (die Historie folgt), definieren Sie die Ports, lassen Sie Shims an den alten Pfaden zurück, verdrahten Sie die drei Aliase und führen Sie dann die Gates aus. Extrahieren Sie kein Feature, an dem Sie gerade aktiv iterieren.
  • Lizenzierung: Jedes Paket steht unter AGPL-3.0-or-later, wie die App.

src/ behält alles, was wirklich die Frontend-App ausmacht: die Zustand-Stores, den Tauri-IPC-Client (src/lib/tauri.ts), das shadcn-UI-Kit, Layout-/Shell-Komponenten, die Port-Adapter und Shims, src/contributions/ und Feature-Klebstoff, der naturgemäß an Stores koppelt (das Preflight-Panel-UI, das Inline-KI-Bearbeitungs-Plugin sowie Code- oder Hover-Intelligenz, die als extraExtensions in den Editor injiziert wird). Tauri-Befehle und die Integration in das Betriebssystem bleiben in src-tauri/. Provider-Clients, Streaming und Agent-Orchestrierung gehören in crates/oleafly-agent/, damit Secrets und Provider-Anfragen außerhalb der Webview bleiben.

Bildvorschau

KI-Antworten konnten nicht geladen werden

Versuchen Sie es erneut oder durchsuchen Sie die Dokumentation.