Перейти до вмісту
Oleaflyv0.4.7

Як у Oleafly настільний застосунок поділено між фронтенд-робочим простором, сервісами Rust і зафіксованими sidecar-бінарниками.

Фронтенд Oleafly — це робочий простір pnpm: оболонка застосунку живе в корені репозиторію (src/), а багаторазовий фронтенд-код — у packages/* у вигляді дванадцяти вузькоспрямованих пакетів. Бекенд Tauri живе в src-tauri/, а багаторазовий код провайдерів ШІ та агентів — у crates/oleafly-agent/. Див. схему архітектури в README (відкривається в новій вкладці) для загальної картини фронтенд/Rust/sidecar і сторінку Розробка щодо щоденних команд.

Цей документ охоплює: що собою являє кожен пакет, правила інверсії залежностей, які зберігають їх чистими, реєстр внесків, що під’єднує можливості до оболонки, як працює розв’язання модулів, а також як додати або винести пакет.

Навіщо робочий простір

Розділ «Навіщо робочий простір»

Колись кожна можливість напряму дотягувалася до сторів Zustand застосунку, до клієнта Tauri IPC і до UI-кітів shadcn. Це робило можливості невіддільними від оболонки: нічого не можна було тестувати, повторно використовувати чи осмислювати окремо. Робочий простір інвертує цю ситуацію. Пакети тримають логіку можливостей і оголошують вузькі інтерфейси, названі портами, для всього, що їм потрібно. Застосунок реалізує ці порти поверх своїх сторів і клієнта Tauri та впроваджує їх. Застосунок залежить від пакетів, але ніколи навпаки.

Пакети споживаються як вихідний код TypeScript (їхній main вказує на src/index.ts). Ніякого етапу збірки, публікацій чи розбіжностей версій: Vite, Vitest і tsc розв’язують @oleafly/* одразу до вихідних кодів пакетів через аліаси (див. Розв’язання модулів).

ПакетЩо цеВпроваджувані портиКлючові залежності
@oleafly/latexЧиста логіка LaTeX/рисунків: модель діаграм, серіалізатор TikZ (modelToTikz, запис-читання вбудованої моделі), конструктор автономних документів (buildStandaloneDoc), допоміжні функції для імен рисунків і їхніх байтівнемає (чистий)немає
@oleafly/ai-coreМетадані провайдерів для відображення та вибір моделей на фронтенді, що утримуються в паритеті з каталогом Rustнемає (чистий)немає
@oleafly/latex-intelligenceВалідовані типи та доступ до даних корпусу LaTeX, який використовують інтелектуальні функції редакторанемає (чистий)немає
@oleafly/registryРеєстр внесків: вкладки рейки, команди палітри/омнібара, набори інструментів ШІ (подробиці нижче)немає (внески самі несуть свою поведінку)react (типи)
@oleafly/preflightРушій передперевірки: типізація документів, правила для джерел/PDF/посилань, розбір ATS, підготовка доступного експорту, оцінювання. pdf-extract постачається як підшлях @oleafly/preflight/pdf-extract, щоб pdf.js не потрапляв у тестові графи nodeнемає (чистий)pdfjs-dist (лише підшлях)
@oleafly/pdf-to-latexЛокальний конвеєр відтворення PDF у LaTeX: групування рядків, виявлення заголовків і колонок, визначення стилю, відображення символів, витягання рисунків, екранування та збирання документа. Адаптер pdf.js постачається як підшлях @oleafly/pdf-to-latex/pdf-adapter з тієї самої причини щодо тестових графів nodeнемає (чистий)pdfjs-dist (лише підшлях)
@oleafly/wysiwygЯдро обмеженого візуального редактора: спільна схема, розбір/серіалізація LaTeX і Markdown, а також сирі вузли inline/block, які зберігають непідтримуване джерело видимим замість того, щоб його відкидатинемає (чистий)немає
@oleafly/editorЯдро CodeMirror для LaTeX, Typst і Markdown: CodeMirrorEditor, контролер редагування (insertAtCursor, gotoLine, …), мови + автодоповнення, тема, згортання коду, закріплені заголовки, лінтери, latex-mask, передперегляд математики (KaTeX для LaTeX, Typst для Typst), допоміжні засоби редагування Typst і перетворення математики LaTeX у Typst, вбудований режим «Візуально» для LaTeX і Typst (@oleafly/editor/visual), панель пошуку, лінтери орфографії/граматикиEditorHost (модель документа + налаштування, у формі хуків), SpellHost (перевірка орфографії/Harper/словник), setBibKeysProvider, extraExtensions/extraKeymap@codemirror/*, @replit/codemirror-vim, @replit/codemirror-emacs, katex
@oleafly/previewВіртуалізований переглядач pdf.js (PdfViewer), контролер сторінок SyncTeX (gotoRect, pageClickToBp), воркер pdf.js і поліфіли WebView (@oleafly/preview/polyfills, імпортується першим у main.tsx)проп onOpenLink (посилання, що відкриваються в системному браузері), setPdfLoggerpdfjs-dist
@oleafly/diagramВізуальний композер діаграм для TikZ, Typst (fletcher) і Mermaid: полотно React Flow, інспектор фігур, вкладки «Малювання»/«Код», потік компіляція-перегляд-вставка. Кожна мова в src/languages/ має записувач, зчитувач і свої форми вставки та роботи з файлами, а полотно і код лишаються синхронними в обидва бокиDiagramHost (компіляція LaTeX, рендеринг Typst і Mermaid, файлове введення-виведення, вставка в редактор, виправлення ШІ), контекст DiagramKit (Button/Tooltip/Select/toast/тема)@xyflow/react, @codemirror/*, @oleafly/latex, @oleafly/editor
@oleafly/ai-toolsВизначення інструментів для проєктів, досліджень, пам’яті та рисунків, із мутаціями, що враховують підтвердженняAiToolsHost (файли, компіляція, індекс символів, конвеєр рисунків, редактор)@oleafly/latex
@oleafly/templatesГалерея шаблонів нових проєктів (двокроковий майстер, категорії, передперегляди, разові завантаження ресурсів із прогресом)TemplatesHost (передперегляди, завантаження ресурсів, журналювання), TemplatesKit (Button/Tooltip), параметри кольорів як пропсинемає, крім UI-утиліт

Напрямок залежностей, строго контрольований:

Як оболонка застосунку й пакети залежать одне від одногоОболонка застосунку в src/ містить сховища Zustand, набір UI, клієнт Tauri й адаптери портів. Адаптери реалізують порти, які оголошують функціональні пакети @oleafly, як-от EditorHost, і застосунок їх впроваджує. Функціональні пакети можуть залежати від чистих листових пакетів, як-от latex, ai-core і registry. Клієнт Tauri спілкується з бекендом на Rust через Tauri IPC. Пакети ніколи не імпортують застосунок, його сховища чи API Tauri.Оболонка застосунку, src/Пакети @oleafly/*СховищаZustandАдаптериРеалізують портиНабір UIshadcnКлієнт Taurisrc/lib/tauri.tsФункціональні пакетиОголошують порти, як-от EditorHostЛистові пакетиlatex, ai-core, registryБекенд на Rustsrc-tauri, crates/oleafly-agentВпроваджуєTauri IPCПакети ніколи не імпортують застосунок, його сховища чи API Tauri.Як оболонка застосунку й пакети залежать одне від одногоОболонка застосунку в src/ містить сховища Zustand, набір UI, клієнт Tauri й адаптери портів. Адаптери реалізують порти, які оголошують функціональні пакети @oleafly, як-от EditorHost, і застосунок їх впроваджує. Функціональні пакети можуть залежати від чистих листових пакетів, як-от latex, ai-core і registry. Клієнт Tauri спілкується з бекендом на Rust через Tauri IPC. Пакети ніколи не імпортують застосунок, його сховища чи API Tauri.Оболонка застосунку, src/Пакети @oleafly/*СховищаНабір UIКлієнт TauriАдаптериБекенд на Rustsrc-tauri, cratesФункціональні пакетиОголошують портиЛистові пакетиlatex, ai-core, registryTauri IPCВпроваджуєПакети ніколи не імпортують застосунок,його сховища чи API Tauri.
Застосунок залежить від пакетів і впроваджує адаптери для оголошених ними портів. Пакети ніколи не імпортують застосунок.

Пакет ніколи не повинен імпортувати з src/ (@/…), зі стору Zustand, з @tauri-apps/* або з UI-компонентів застосунку. Перевірка чистоти — частина гігієни винесення:

Вікно термінала
grep -rn 'from "@/\|@tauri\|zustand' packages/*/src # must return nothing

Пакети оголошують інтерфейси. Застосунок реалізує їх один раз, зазвичай як об’єкт-адаптер на рівні модуля, що замикається на своїх сторах. Чотири повторювані форми:

Хост-порти: один interface XHost на пакет, що покриває потрібні йому сервіси. Застосунок будує адаптер з імперативними читаннями сторів:

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

Те, що клопоти, властиві лише застосунку, лишаються всередині адаптера, також тримає залежності поза пакетами. Облікові дані провайдерів і запити лишаються в Rust, тож фронтенд-пакети отримують лише стан і результати, яких вимагають їхні інтерфейси.

UI-кіти: React-контекст (або проп) зі структурно типізованими компонентами: Button: ComponentType<{ variant?: …; onClick?: () => void }>. Застосунок пропускає свої компоненти shadcn без змін, бо менший структурний тип сумісний. Кіти також несуть toast і хук useThemeMode().

Порти у формі хуків: для пакетів, які мають підписуватися на стан застосунку. EditorHost редактора має члени-хуки (useActivePath(), useSettings()) плюс імперативних двійників (getActivePath()) для слухачів подій. Правила: об’єкт host повинен бути на рівні модуля (стабільна тотожність), а його члени use* мають викликати хуки безумовно.

Модульні синглтони: для наскрізного зв’язування setPdfLogger(fn), setSpellHost(host), setBibKeysProvider(fn) мають стандартні реалізації no-op і встановлюються шимом застосунку як побічний ефект імпорту.

Кожне винесення лишало тонкий файл за старим шляхом @/…, який реекспортує (або обгортає) пакет, тож споживачі та наявні шляхи vi.mock(...) ніколи не зазнавали змін. Приклади: src/components/editor/cm/controller.ts (реекспорт), src/components/pdf/PdfViewer.tsx (обгортка, що впроваджує відкривач посилань shell-плагіна), src/lib/ai-tools.ts (адаптер + початкові сигнатури фабрик). Новий код може імпортувати @oleafly/* безпосередньо. Шими існують заради сумісності, а не як бажаний шлях.

@oleafly/registry — це спосіб, у який можливості під’єднуються до оболонки застосунку, не вимагаючи, щоб оболонка про них знала. Три види внесків:

  • Вкладки рейки (registerRailTab): іконка, розділ (explore / review / assist, розділювачі малюються між непорожніми розділами), необов’язкова видимість when(ctx), необов’язковий хук useBadge() (наприклад, кількість змін у Git) і компонент бічної панелі panel.
  • Команди (registerCommand): подаються до палітри команд (Cmd+K, групуються за group) та/або до омнібара (Cmd+P) через surfaces. Мітки й іконки можуть бути функціями від контексту (наприклад, «Перемкнутися на світлу тему»).
  • Набори інструментів ШІ (registerAiToolset): режим чату ("chat", "figure"), зіставлений із фабрикою набору інструментів. Панель чату шукає набір інструментів активного режиму замість жорстко прописаного.

AppContext ({ projectId, projectKind, theme }) будується кожною поверхнею рендерингу з її власних підписок, тож умови when/label залишаються реактивними.

Реєстрація статична: src/contributions/ реєструє кожен вбудований елемент (вкладки, команди, набори інструментів), а main.tsx викликає registerContributions() один раз перед монтуванням оболонки. Rail, Sidebar, CommandPalette, SearchOmnibar і ChatPanel рендерять лише те, що зареєстровано.

Щоб додати вкладку чи команду можливості, зареєструйте її. Без правок оболонки:

// 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"),
});

Це внутрішній API, а не SDK для плагінів: форми можуть вільно змінюватися, і нічого не завантажується динамічно. Відкладено до появи справжнього споживача: формати меню експорту, схеми налаштувань і поведінка за видами проєктів як внески.

Розв’язання модулів

Розділ «Розв’язання модулів»

Оскільки пакети споживаються як вихідний код, той самий аліас оголошується у трьох місцях, і всі три мають лишатися узгодженими під час додавання пакета:

  1. tsconfig.json → compilerOptions.paths. Підшляхи на кшталт @oleafly/preflight/pdf-extract потребують окремого явного запису.
  2. vite.config.ts → resolve.alias. Рядкові аліаси збігаються за префіксом, тож підшляхи розв’язуються без додаткового запису.
  3. vitest.config.ts → той самий resolve.alias, плюс test.include повинен охоплювати packages/**/*.test.ts. Інакше перенесені тести непомітно випадають із прогону.

Також: кореневий package.json залежить від кожного пакета через "workspace:*", а в src/styles/globals.css є @source "../../packages", щоб Tailwind v4 сканував вихідні коди пакетів у пошуках імен класів.

Імпорти, притаманні лише Vite (воркери ?worker&url, імпорти CSS), працюють без змін із вихідних кодів пакетів, бо аліаси вказують на джерело. Тримайте все, що тягне за собою pdf.js, поза барелами, які імпортують тести в середовищі node: саме тому pdf-extract і переглядач пакета preview доступні лише там, де існує DOM.

  • Тести лежать поруч із вихідним кодом (packages/*/src/*.test.ts) і виконуються тим самим pnpm test, що й тести застосунку. Стежте за кількістю тестів, коли переміщуєте файли.
  • Залежності оголошуються нестрого ("react": "*"). pnpm дедуплікує їх щодо кореня, де живуть справжні мінімальні версії. Не оголошуйте залежностей, яких пакет не імпортує.
  • Перевірки для будь-якої зміни пакета: pnpm exec tsc --noEmit, pnpm build, pnpm test (незмінна кількість) і наведений вище grep чистоти.
  • Винесення нового: перемістіть файли через git mv (за ними слідує історія), визначте порти, лишіть шими за старими шляхами, під’єднайте три аліаси, а тоді запустіть перевірки. Не виносьте можливість, над якою активно працюєте.
  • Ліцензування: кожен пакет — AGPL-3.0-or-later, як і застосунок.

Що лишається в застосунку

Розділ «Що лишається в застосунку»

У src/ лишається все, що є власне фронтенд-застосунком: стори Zustand, клієнт Tauri IPC (src/lib/tauri.ts), UI-кіт shadcn, компоненти макета/оболонки, адаптери портів і шими, src/contributions/, а також зв’язувальний код можливостей, який за своєю природою прив’язаний до сторів (UI панелі передперевірки, плагін вбудованого редагування ШІ та інтелектуальні функції для коду й наведення, впроваджені в редактор як extraExtensions). Команди Tauri та інтеграція з операційною системою лишаються в src-tauri/. Клієнти провайдерів, потокова передача та оркестрація агентів належать до crates/oleafly-agent/, щоб секрети й запити до провайдерів залишалися поза webview.

Перегляд зображення

Не вдалося завантажити відповіді ШІ

Спробуйте ще раз або скористайтеся пошуком у документації.