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

# Architecture

> How plugins integrate with the Stratos shell.

Stratos is built on a **shell + plugin** model. The shell owns the infrastructure — auth, routing, simulator connections, IPC bridge, theming, and the sidebar. Plugins are independently deployable modules that extend the shell with custom UI pages and/or background services.

## Shell-Plugin Contract

The shell and plugins communicate through a strict, versioned contract. Plugins never import from the shell directly — all access goes through the SDK's context objects and hooks, which the shell injects at runtime.

Each plugin runs in its own host — errors in one plugin don't crash others. IPC channels are automatically namespaced to `plugin:{pluginId}:*` to prevent collisions.

## Plugin Lifecycle

Plugins go through four stages from discovery to teardown:

### 1. Discovery

On startup, the shell scans the plugins directory and reads each plugin's `plugin.json` manifest. The sidebar order is determined by the VA owner's configuration on the Skyvex platform.

### 2. Load

The shell loads the background module (if `background/index.js` exists in the plugin's install directory) for each discovered plugin. This happens after auth is initialised, so the background module receives a fully populated `PluginContext`.

If `onStart()` throws, the plugin is marked as errored. Other plugins continue loading normally.

### 3. Mount

When a pilot navigates to a plugin's sidebar entry, the shell lazy-loads the UI module and mounts it inside a `PluginShellProvider`. This provider supplies `PluginUIContext` to all child components via React context.

The UI module is unmounted when the pilot navigates away, but the background module keeps running.

### 4. Unmount / Stop

On shell shutdown, `onStop()` is called for each running background module. Use this to close connections, flush data, or cancel timers.

## UI Module vs Background Module

|                   | UI Module                                       | Background Module                              |
| ----------------- | ----------------------------------------------- | ---------------------------------------------- |
| **Where it runs** | Electron renderer process (React)               | Electron main process (Node.js)                |
| **Entry point**   | `src/ui/index.tsx`                              | `src/background/index.ts`                      |
| **Lifecycle**     | Mounted/unmounted on navigation                 | Runs from startup to shutdown                  |
| **Context**       | `PluginUIContext` via `usePluginContext()`      | `PluginContext` passed to `onStart()`          |
| **Use for**       | Display, interaction, flight data visualisation | Express routes, IPC handlers, background tasks |

Most plugins only need a UI module. Create a background module when you need persistent server-side logic — registering API routes, maintaining a WebSocket connection, or running scheduled tasks.

## `PluginContext`

Passed to your background module's `onStart(ctx)` function. Provides scoped access to shell infrastructure:

```typescript theme={null}
type PluginContext = {
  logger: PluginLogger;
  config: PluginConfigStore;
  ipc: PluginIPCRegistrar;
  auth: PluginAuthAccessor;
  airline: PluginAirlineAccessor;
  server: PluginServerRegistrar;
  database: PluginDatabaseAccessor;
  flight: PluginFlightAccessor;
  notify: PluginNotifier;
  dialog: PluginDialogApi;
  prompt: PluginPromptApi;
  plugin: PluginSelfApi;
};
```

All config keys are scoped to your plugin — you can't read or write another plugin's config. All IPC channels are prefixed as `plugin:{pluginId}:*` — you can't accidentally handle another plugin's messages.

## `PluginUIContext`

Available in renderer components via `usePluginContext()`. Provides synchronous access to the same capabilities as `PluginContext`, plus navigation and UI utilities:

```typescript theme={null}
type PluginUIContext = {
  vaApi: AxiosInstance;
  pluginId: string;
  auth: {
    isAuthenticated: boolean;
    token: string | null;
    user: PluginPilotUser | null;
  };
  airline?: {
    id: string;
    name: string;
    icao: string;
    logo_light: string;
    logo_dark: string;
    base_url: string;
    token: string | null;
  } | null;
  config: {
    get<T>(key: string): T | undefined;
    get<T>(key: string, defaultValue: T): T;
  };
  socket: {
    connected: boolean;
    emit(event: string, data: unknown): void;
    on(event: string, handler: (data: unknown) => void): void;
    off(event: string, handler: (data: unknown) => void): void;
  };
  navigation: PluginNavigationHelper;
  toast: PluginToastAPI;
  logger: PluginLogger;
};

type PluginPilotUser = {
  dbID: number;
  pilotID: string;
  firstName: string;
  lastName: string;
  email: string;
  rank: string;
  rankLevel: number;
  rankImage: string;
  avatar: string;
};
```

`vaApi` is a pre-authenticated Axios instance for the bound airline's API, shared with the shell so concurrent `401`s coalesce into a single token refresh. Reach it through the `useVaApi()` hook rather than reading it off the context directly.

Note that `config.get()` is **synchronous** in the UI context (values are pre-loaded when the plugin mounts), while `ctx.config.get()` in the background context returns a `Promise`.

## IPC Communication Model

The shell's IPC bridge auto-prefixes all channel names to prevent cross-plugin pollution.

When your background module registers a handler:

```ts theme={null}
ctx.ipc.handle("get-status", () => ({ online: true }));
```

The actual IPC channel registered is `plugin:my-plugin:get-status`. This means two plugins can both register a `"get-status"` handler without conflict.

To communicate between your UI and background modules, register Express routes in the background module via `ctx.server.registerRouter()` and fetch them in the UI with TanStack Query. This keeps data flowing through standard HTTP rather than requiring a custom IPC abstraction.
