# फ्रंटएंड आर्किटेक्चर

Oleafly डेस्कटॉप ऐप को फ्रंटएंड वर्कस्पेस, Rust सर्विसेज़ और पिन किए गए साइडकार में कैसे बाँटता है।

Oleafly का फ्रंटएंड एक pnpm वर्कस्पेस है: ऐप शेल रिपॉज़िटरी रूट (`src/`) पर रहता है, और पुनः प्रयोग योग्य फ्रंटएंड कोड `packages/*` में बारह केंद्रित पैकेजों के रूप में रहता है। Tauri बैकएंड `src-tauri/` में रहता है, जबकि पुनः प्रयोग योग्य AI प्रोवाइडर और एजेंट कोड `crates/oleafly-agent/` में रहता है। फ्रंटएंड/Rust/साइडकार की पूरी तस्वीर के लिए [README का आर्किटेक्चर डायग्राम](https://github.com/Oleafly/Oleafly/blob/main/README.md#architecture) देखें और रोज़मर्रा के कमांड के लिए [Development](/docs/engineering/development/) देखें।

यह दस्तावेज़ बताता है: हर पैकेज क्या है, उन्हें शुद्ध रखने वाले डिपेंडेंसी-इनवर्ज़न नियम, फीचर को शेल से जोड़ने वाला कंट्रीब्यूशन रजिस्ट्री, मॉड्यूल रिज़ॉल्यूशन कैसे काम करता है, और कोई पैकेज कैसे जोड़ें या निकालें।

## वर्कस्पेस क्यों

पहले हर फीचर सीधे ऐप के Zustand स्टोर, Tauri IPC क्लाइंट और shadcn UI किट तक पहुँचता था। इससे फीचर शेल से अलग नहीं हो पाते थे: किसी चीज़ को अकेले टेस्ट, पुनः प्रयोग या समझा नहीं जा सकता था। वर्कस्पेस इसे उलट देता है। पैकेज फीचर लॉजिक रखते हैं और अपनी हर ज़रूरत के लिए पोर्ट कहलाने वाले संकरे इंटरफ़ेस घोषित करते हैं। ऐप उन पोर्ट को अपने स्टोर और Tauri क्लाइंट पर लागू करके इंजेक्ट करता है। ऐप पैकेजों पर निर्भर रहता है, कभी उल्टा नहीं।

पैकेज **TypeScript सोर्स के रूप में** उपयोग होते हैं (उनका `main` `src/index.ts` की ओर इशारा करता है)। कोई बिल्ड स्टेप नहीं, कोई प्रकाशन नहीं, और कोई वर्शन स्क्यू नहीं: Vite, Vitest और `tsc` तीनों एलाइज़ेज़ के ज़रिए `@oleafly/*` को सीधे पैकेज सोर्स पर रिज़ॉल्व करते हैं ([मॉड्यूल रिज़ॉल्यूशन](#module-resolution) देखें)।

## पैकेज

| पैकेज | यह क्या है | इंजेक्ट किए जाने वाले पोर्ट | प्रमुख डिपेंडेंसीज़ |
| --- | --- | --- | --- |
| `@oleafly/latex` | शुद्ध LaTeX/फ़िगर लॉजिक: डायग्राम मॉडल, TikZ सीरियलाइज़र (`modelToTikz`, embedded-model राउंड-ट्रिप), स्टैंडअलोन-डॉक बिल्डर (`buildStandaloneDoc`), फ़िगर नाम/बाइट हेल्पर | कोई नहीं (शुद्ध) | कोई नहीं |
| `@oleafly/ai-core` | प्रोवाइडर डिस्प्ले मेटाडेटा और फ्रंटएंड मॉडल चयन, Rust कैटलॉग के साथ समानता में रखा गया | कोई नहीं (शुद्ध) | कोई नहीं |
| `@oleafly/latex-intelligence` | एडिटर इंटेलिजेंस द्वारा उपयोग किए जाने वाले LaTeX कॉर्पस के लिए वैलिडेटेड टाइप और डेटा एक्सेस | कोई नहीं (शुद्ध) | कोई नहीं |
| `@oleafly/registry` | कंट्रीब्यूशन रजिस्ट्री: रेल टैब, पैलेट/ऑम्निबार कमांड, AI टूलसेट ([विवरण नीचे](#the-contribution-registry)) | कोई नहीं (कंट्रीब्यूशन अपना व्यवहार स्वयं रखते हैं) | react (types) |
| `@oleafly/preflight` | प्रीफ़्लाइट इंजन: डॉक्यूमेंट टाइपिंग, सोर्स/PDF/रेफ़रेंस नियम, ATS पार्सिंग, एक्सेसिबल-एक्सपोर्ट तैयारी, स्कोरिंग। `pdf-extract` `@oleafly/preflight/pdf-extract` सबपाथ के रूप में आता है ताकि pdf.js node टेस्ट ग्राफ़ से बाहर रहे | कोई नहीं (शुद्ध) | `pdfjs-dist` (सिर्फ़ सबपाथ) |
| `@oleafly/pdf-to-latex` | लोकल PDF-to-LaTeX पुनर्निर्माण पाइपलाइन: लाइन ग्रुपिंग, हेडिंग और कॉलम पहचान, स्टाइल अनुमान, सिंबल मैपिंग, फ़िगर एक्सट्रैक्शन, एस्केपिंग और डॉक्यूमेंट असेंबली। pdf.js एडाप्टर उसी node-test-graph कारण से `@oleafly/pdf-to-latex/pdf-adapter` सबपाथ के रूप में आता है | कोई नहीं (शुद्ध) | `pdfjs-dist` (सिर्फ़ सबपाथ) |
| `@oleafly/wysiwyg` | सीमित विज़ुअल एडिटर कोर: साझा स्कीमा, LaTeX और Markdown पार्स/सीरियलाइज़, और रॉ इनलाइन/ब्लॉक नोड जो असमर्थित सोर्स को हटाने के बजाय दिखाई देने देते हैं | कोई नहीं (शुद्ध) | कोई नहीं |
| `@oleafly/editor` | LaTeX, Typst और Markdown के लिए CodeMirror कोर: `CodeMirrorEditor`, एडिटर-व्यू कंट्रोलर (`insertAtCursor`, `gotoLine`, …), भाषाएँ + कंप्लीशन, थीम, फोल्डिंग, स्टिकी हेडिंग, लिंटर, latex-mask, मैथ प्रीव्यू (LaTeX के लिए KaTeX, Typst के लिए Typst), Typst एडिटिंग सहायक और LaTeX-to-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` प्रॉप (सिस्टम-ब्राउज़र लिंक), `setPdfLogger` | `pdfjs-dist` |
| `@oleafly/diagram` | TikZ, Typst (fletcher) और Mermaid के लिए विज़ुअल डायग्राम कंपोज़र: React Flow कैनवास, शेप इंस्पेक्टर, Draw/Code टैब, compile-preview-insert प्रवाह। `src/languages/` की हर भाषा का एक राइटर, एक रीडर और उसके इंसर्ट और फ़ाइल रूप हैं, और कैनवास और कोड दोनों दिशाओं में एक साथ चलते रहते हैं | `DiagramHost` (LaTeX कंपाइल, Typst और Mermaid रेंडरिंग, फ़ाइल IO, एडिटर इंसर्ट, AI फ़िक्स), `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 स्टोर, `@tauri-apps/*` या ऐप के UI कंपोनेंट से इंपोर्ट न करे। शुद्धता जाँच एक्सट्रैक्शन स्वच्छता का हिस्सा है:

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

## पोर्ट पैटर्न

पैकेज इंटरफ़ेस घोषित करते हैं। ऐप उन्हें एक बार लागू करता है, आमतौर पर एक मॉड्यूल-स्तरीय एडाप्टर ऑब्जेक्ट के रूप में जो अपने स्टोर को बंद करके रखता है। चार बार-बार आने वाले रूप:

**होस्ट पोर्ट**: हर पैकेज के लिए एक `interface XHost` जो उसे चाहिए वाली सर्विसेज़ को समेटे। ऐप इम्परेटिव स्टोर रीड के साथ एडाप्टर बनाता है:

```ts
// 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()`) हैं। नियम: होस्ट ऑब्जेक्ट मॉड्यूल-स्तर का होना चाहिए (स्थिर पहचान) और उसके `use*` मेंबर हुक को बिना शर्त कॉल करने चाहिए।

**मॉड्यूल सिंगलटन**: क्रॉस-कटिंग ग्लू के लिए, `setPdfLogger(fn)`, `setSpellHost(host)`, `setBibKeysProvider(fn)` के नो-ऑप डिफ़ॉल्ट होते हैं और ऐप शिम उन्हें इंपोर्ट के साइड इफ़ेक्ट के रूप में इंस्टॉल करता है।

### शिम

हर एक्सट्रैक्शन पुराने `@/…` पाथ पर एक पतली फ़ाइल छोड़ता है जो पैकेज को फिर से एक्सपोर्ट (या रैप) करती है, ताकि उपभोक्ता और मौजूदा `vi.mock(...)` पाथ कभी न बदलें। उदाहरण: `src/components/editor/cm/controller.ts` (री-एक्सपोर्ट), `src/components/pdf/PdfViewer.tsx` (रैपर जो शेल-प्लगइन लिंक ओपनर इंजेक्ट करता है), `src/lib/ai-tools.ts` (एडाप्टर + मूल फ़ैक्टरी सिग्नेचर)। नया कोड `@oleafly/*` को सीधे इंपोर्ट कर सकता है। शिम संगतता के लिए हैं, पसंदीदा रास्ता बनने के लिए नहीं।

## कंट्रीब्यूशन रजिस्ट्री

`@oleafly/registry` के ज़रिए फीचर ऐप शेल से जुड़ते हैं बिना शेल को उनकी जानकारी के। तीन तरह के कंट्रीब्यूशन:

- **रेल टैब** (`registerRailTab`): आइकन, सेक्शन (`explore` / `review` / `assist`, गैर-खाली सेक्शन के बीच डिवाइडर बनते हैं), वैकल्पिक `when(ctx)` दृश्यता, वैकल्पिक `useBadge()` हुक (जैसे Git बदलाव संख्या), और साइडबार `panel` कंपोनेंट।
- **कमांड** (`registerCommand`): कमांड पैलेट (Cmd+K, `group` के अनुसार ग्रुप किए गए) और/या `surfaces` के ज़रिए ऑम्निबार (Cmd+P) तक पहुँचाए जाते हैं। लेबल और आइकन कॉन्टेक्स्ट के फ़ंक्शन हो सकते हैं (जैसे "Switch to light theme")।
- **AI टूलसेट** (`registerAiToolset`): एक चैट मोड (`"chat"`, `"figure"`) जो एक टूलसेट फ़ैक्टरी से मैप होता है। चैट पैनल सक्रिय मोड का टूलसेट खोजता है, किसी एक को हार्ड-कोड नहीं करता।

`AppContext` (`{ projectId, projectKind, theme }`) हर रेंडरिंग सरफ़ेस अपनी सदस्यताओं से बनाता है, इसलिए `when`/`label` शर्तें रिएक्टिव बनी रहती हैं।

रजिस्ट्रेशन **स्टैटिक** है: `src/contributions/` हर बिल्ट-इन (टैब, कमांड, टूलसेट) रजिस्टर करता है और `main.tsx` शेल माउंट होने से पहले `registerContributions()` को एक बार कॉल करता है। `Rail`, `Sidebar`, `CommandPalette`, `SearchOmnibar` और `ChatPanel` सिर्फ़ रजिस्टर की गई चीज़ें रेंडर करते हैं।

किसी फीचर का टैब या कमांड जोड़ने के लिए उसे रजिस्टर करें। शेल में कोई बदलाव नहीं:

```ts
// 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` और प्रीव्यू पैकेज का व्यूअर सिर्फ़ वहीं पहुँच योग्य हैं जहाँ 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`), shadcn UI किट, लेआउट/शेल कंपोनेंट, पोर्ट एडाप्टर और शिम, `src/contributions/`, और स्वभाव से स्टोर से जुड़ा फीचर ग्लू (प्रीफ़्लाइट पैनल UI, इनलाइन-AI एडिटिंग प्लगइन, और कोड या होवर इंटेलिजेंस जो `extraExtensions` के रूप में एडिटर में इंजेक्ट होता है)। Tauri कमांड और ऑपरेटिंग-सिस्टम इंटीग्रेशन `src-tauri/` में रहते हैं। प्रोवाइडर क्लाइंट, स्ट्रीमिंग और एजेंट ऑर्केस्ट्रेशन `crates/oleafly-agent/` में रहते हैं ताकि सीक्रेट और प्रोवाइडर रिक्वेस्ट वेबव्यू के बाहर रहें।
