> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lynxhub.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Extension architecture

> Understand the architecture and runtime environment of LynxHub extensions.

LynxHub extensions are dynamic plugins designed to integrate with the core application. They allow you to customize the shell UI, add routes, inject Redux reducers, listen to application events, and add system tray menu items.

***

## Process separation

LynxHub operates on a multi-process architecture based on Electron. Your extension runs code in two separate processes:

```mermaid theme={null}
graph TD
    subgraph Electron Main Process (Node.js)
        HostMain[Host Main Process] <-->|IPC Events| ExtensionMain["Extension Main (TypeScript Source)"]
        ExtensionMain -->|Compiled / Bundled| CjsBundle["scripts/main/mainEntry.cjs"]
    end
    subgraph Electron Renderer Process (Chromium)
        HostRenderer[Host React Shell] <-->|Module Federation| ExtensionRenderer["Extension Renderer (TypeScript Source)"]
        ExtensionRenderer -->|Compiled / Bundled| MjsBundle["scripts/renderer/rendererEntry.mjs"]
    end
```

### Main process

The main process runs in a Node.js environment. It has full access to operating system APIs.

* **Entrypoint**: `src/main/lynxExtension.ts` (compiled and bundled to `scripts/main/mainEntry.cjs` as a CommonJS module).
* **Responsibility**: Listens to system-level lifecycles, registers global IPC channels, adds tray menu items, and launches background processes (e.g., via `node-pty`).
* **Signature**:

  ```typescript theme={null}
  import {ExtensionMainApi, MainExtensionUtils, MainIpcApi} from '@lynx_main/plugins/extensions/types';

  export async function initialExtension(lynxApi: ExtensionMainApi, utils: MainExtensionUtils, mainIpc: MainIpcApi): Promise<void> {
    // Register main process listeners and lifecycles
  }
  ```

### Renderer process

The renderer process displays the graphical interface and runs in a Chromium browser window.

* **Entrypoint**: `src/renderer/Extension.tsx` (compiled and bundled to `scripts/renderer/rendererEntry.mjs` as an ES module).
* **Responsibility**: Injects React UI components into predefined slots (Title Bar, Status Bar, Settings, Sidebar), adds custom pages/routes, and hooks into state management.
* **Signature**:

  ```typescript theme={null}
  import {ExtensionRendererApi} from '@lynx/plugins/extensions/types/api';
  import {RendererIpcApi} from '@lynx/plugins/extensions/types/ipcWrapper';

  export function InitialExtensions(lynxAPI: ExtensionRendererApi, rendererIpc: RendererIpcApi, extensionId: string): void {
    // Inject React components, custom Redux reducers, or event listeners
  }
  ```

***

## Boot and loading sequence

LynxHub follows a structured sequence when loading extensions during application startup:

```mermaid theme={null}
sequenceDiagram
    participant App as App Lifecycle
    participant EM as ExtensionManager
    participant EA as ExtensionApi
    participant Ext as Extension Code

    App->>EM: Initialize plugin managers
    Note over EM: Scan and validate plugin folders
    EM->>Ext: Import extension main module
    EM->>Ext: Invoke initialExtension(lynxApi, utils, mainIpc)
    Note over Ext: Register lifecycle callbacks
    App->>App: Complete app boot
    App->>EM: app.whenReady() fires
    EM->>EA: Trigger onAppReady() callbacks
    EA->>Ext: Execute registered onAppReady callbacks
    App->>EM: Register IPC listeners
    EM->>EA: Trigger listenForChannels() callbacks
    EA->>Ext: Execute registered listenForChannels callbacks
    App->>App: Show main window
    App->>EM: window ready-to-show fires
    EM->>EA: Trigger onReadyToShow() callbacks
    EA->>Ext: Execute registered onReadyToShow callbacks
```

### 1. Discovery and validation

During startup, the host scans the app `Plugins/` folder. It checks for a valid subdirectory containing `metadata.json` and the required entrypoint files.

### 2. Main process initialization

The host imports the backend entrypoint of the extension. It calls the exported `initialExtension` function and passes the API wrappers. At this point, the extension registers callbacks for subsequent lifecycle events.

### 3. Application ready hook

When Electron completes its initialization, the host invokes all registered `onAppReady` callbacks.

### 4. IPC channel setup

The host registers core IPC handlers. It then triggers `listenForChannels` callbacks. This allows your extension to register custom IPC event handlers.

### 5. Main window display

When the main application window is ready to be shown, the host triggers `onReadyToShow` callbacks.

***

## Module Federation in the renderer

LynxHub uses **Module Federation** via `@originjs/vite-plugin-federation` to dynamically mount extension interfaces in production. This approach avoids rebuilding the host application.

```mermaid theme={null}
sequenceDiagram
    participant Host as Host Renderer
    participant IPC as Main Process IPC
    participant ExtServer as Local Extension Server

    Host->>IPC: Get active plugin addresses
    IPC-->>Host: Return addresses (e.g., http://localhost:5001)
    Host->>Host: Register remote via __federation_method_setRemote()
    Host->>Host: Import Extension via __federation_method_getRemote()
    Host->>ExtServer: Fetch scripts/renderer/rendererEntry.mjs
    ExtServer-->>Host: Serve extension bundle
    Host->>Host: Run InitialExtensions()
```

### Runtime loading flow

1. **Discovery**: The host requests the active plugin addresses from the main process over IPC.
2. **Registration**: For each active extension, the host registers its remote entry URL (`<address>/scripts/renderer/rendererEntry.mjs`) using the Virtual Module Federation API:
   ```javascript theme={null}
   import {__federation_method_setRemote} from '__federation__';
   __federation_method_setRemote(extensionId, {format: 'esm', from: 'vite', url});
   ```
3. **Mounting**: The host dynamically loads the entry component using `__federation_method_getRemote(extensionId, 'Extension')` and calls the `InitialExtensions` initializer.

### Shared packages

To prevent loading duplicate library instances and to ensure shared context is maintained, the host shares core packages with all extensions:

* `react` and `react-dom`
* `react-redux`
* `@heroui/react` (HeroUI v3 React components)
* `@heroui/styles` (HeroUI v3 styling tokens)
* `react-aria` (React accessibility hooks)

Any package listed above loads from the host shell. Your extension must treat these as peer dependencies. You must configure them as shared in your Vite configuration.

***

## Vite build configuration

To build your extension correctly for LynxHub, configure `@originjs/vite-plugin-federation` and `electron-vite` in your `electron.vite.config.ts` configuration. Below is the standard configuration structure used by the template:

```typescript theme={null}
import federation from '@originjs/vite-plugin-federation';
import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
import {defineConfig} from 'electron-vite';
import {resolve} from 'path';

export default defineConfig({
  main: {
    root: resolve('extension/src/main'),
    build: {
      externalizeDeps: {exclude: ['tree-kill']},
      outDir: resolve('extension_out/main'),
      rolldownOptions: {
        input: resolve('extension/src/main/lynxExtension.ts'),
        output: {entryFileNames: 'mainEntry.cjs', format: 'cjs'},
      },
    },
    resolve: {
      alias: {
        '@lynx_common': resolve(__dirname, '..', 'src/common'),
        '@lynx_main': resolve(__dirname, '..', 'src/main'),
      },
    },
  },
  renderer: {
    root: resolve('extension/src/renderer'),
    plugins: [
      react(),
      tailwindcss(),
      federation({
        name: 'extension',
        filename: 'rendererEntry.mjs',
        exposes: {
          Extension: resolve('extension/src/renderer/Extension.tsx'),
        },
        shared: {
          react: {generate: false},
          'react-dom': {generate: false},
          'react-redux': {generate: false},
          '@heroui/react': {generate: false},
          '@heroui/styles': {generate: false},
          'react-aria': {generate: false},
        },
      }),
    ],
    resolve: {
      alias: {
        '@lynx_module': resolve(__dirname, '..', 'module/src'),
        '@lynx_extension': resolve(__dirname, '..', 'extension/src'),
        '@lynx_common': resolve(__dirname, '..', 'src/common'),
        '@lynx': resolve(__dirname, '..', 'src/renderer/mainWindow'),
        '@lynx_shared': resolve(__dirname, '..', 'src/renderer/shared'),
        '@lynx_assets': resolve(__dirname, '..', 'src/renderer/shared/assets'),
      },
    },
    build: {
      outDir: resolve('extension_out/renderer'),
      rolldownOptions: {
        input: resolve('extension/src/renderer/index.html'),
        treeshake: {moduleSideEffects: false},
      },
      assetsDir: '',
      minify: false,
      target: 'esnext',
      cssCodeSplit: false,
      modulePreload: false,
    },
    publicDir: resolve(__dirname, 'extension/src/renderer/Public'),
  },
});
```

***

## Development vs. production environments

LynxHub provides two separate loading strategies to optimize the developer experience:

| Feature                   | Development mode                                  | Production mode                                       |
| :------------------------ | :------------------------------------------------ | :---------------------------------------------------- |
| **Main entry import**     | `extension/src/main/lynxExtension.ts`             | `scripts/main/mainEntry.cjs` (CommonJS module)        |
| **Renderer entry import** | `@lynx_extension/renderer/Extension` (Vite alias) | `scripts/renderer/rendererEntry.mjs` (dynamic remote) |
| **Loading mechanism**     | Hot-imported from local source folder             | Served by local servers via Module Federation         |
| **Updates**               | Hot Module Replacement (HMR) instantly            | Requires a build and app restart                      |
| **Location**              | Root `/extension` directory of the application    | `<AppData>/Plugins/<extension-id>/` directory         |

### Development mode

To bypass Module Federation and speed up updates, the host process attempts to hot-import the extension directly from your local source directory using the Vite alias:

* **Renderer**: `@lynx_extension/renderer/Extension` (mapped to `/extension/src/renderer`)
* **Main**: `extension/src/main/lynxExtension`

This allows you to benefit from Hot Module Replacement (HMR). You can see UI changes instantly without building or deploying.

### Production mode

In production, LynxHub launches local servers for installed extensions. It loads their entry files from disk:

* **Validation**: LynxHub verifies the directory structure under `<AppData>/Plugins/<id>/`. It ensures `scripts/main/mainEntry.cjs` and `scripts/renderer/rendererEntry.mjs` exist.
* **Protocol**: Static assets and entrypoints are served locally and registered as Module Federation remotes.
