> ## 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.

# Background ↔ UI Communication

> Connecting a background module and a UI module with Express routes, TanStack Query, and IPC push.

A plugin's background and UI modules run in separate processes. Two patterns connect them: **HTTP (Express routes + TanStack Query)** for request/response, and **IPC push** for background-to-UI broadcasts.

## Registering Express Routes

A common pattern is to fetch data from your VA's external API in the background module and expose it locally:

```ts theme={null}
import { createPlugin } from "@skyvexsoftware/stratos-sdk/helpers";
import { Router } from "express";

const router = Router();

router.get("/fleet", async (req, res) => {
  try {
    const response = await fetch("https://api.my-va.com/fleet");
    const data = await response.json();
    res.json({ success: true, data });
  } catch (err) {
    res.status(500).json({ success: false, error: "Failed to fetch fleet" });
  }
});

export default createPlugin({
  async onStart(ctx) {
    ctx.server.registerRouter("/api/my-plugin", router);
    ctx.logger.info("MyPlugin", "Routes registered");
  },

  async onStop(ctx) {
    ctx.logger.info("MyPlugin", "Stopping...");
  },
});
```

In your UI module, query this endpoint with TanStack Query:

```tsx theme={null}
import { useQuery } from "@tanstack/react-query";
import { STRATOS_APP_BASE } from "@skyvexsoftware/stratos-sdk";

function FleetList() {
  const { data, isLoading } = useQuery({
    queryKey: ["fleet"],
    queryFn: () =>
      fetch(`${STRATOS_APP_BASE}/api/my-plugin/fleet`).then((r) => r.json()),
  });

  if (isLoading) return <p>Loading...</p>;
  return <ul>{data?.data?.map((f) => <li key={f.id}>{f.name}</li>)}</ul>;
}
```

## Communication Between Background and UI

For UI-to-background communication, the recommended pattern is **Express routes + TanStack Query**. Register routes in your background module with `ctx.server.registerRouter()` and query them from your UI module using TanStack Query, as shown in the [Registering Express Routes](#registering-express-routes) section above.

This approach composes naturally with React's rendering model — loading states, error handling, caching, and refetching all come for free via TanStack Query.

For background-to-UI push (data the UI didn't ask for, like live updates or alerts), you can use `ctx.ipc.send()`. UI modules subscribe with `socket.on` from `usePluginContext()` — both ends use the leaf event name, the shell scopes the channel to your plugin id automatically.

```ts theme={null}
// Background: push an update to every renderer of this plugin
ctx.ipc.send("status-update", { connected: true, pilots: 42 });
```

```tsx theme={null}
// UI: subscribe in a component
import { usePluginContext } from "@skyvexsoftware/stratos-sdk";
import { useEffect, useState } from "react";

export function StatusBadge() {
  const { socket } = usePluginContext();
  const [status, setStatus] = useState<{
    connected: boolean;
    pilots: number;
  }>();

  useEffect(() => {
    const handler = (payload: unknown) => setStatus(payload as never);
    socket.on("status-update", handler);
    return () => socket.off("status-update", handler);
  }, [socket]);

  return <span>{status?.connected ? "online" : "offline"}</span>;
}
```

A few rules:

* **Leaf names only.** Don't include `plugin:{id}:` in the channel — the shell prefixes both ends, doubling it would broadcast to nothing.
* **Fire-and-forget.** A UI that mounts after a broadcast doesn't see it. Pattern: HTTP fetch on mount, then subscribe for updates.
* **Same plugin only.** Plugin A's UI cannot subscribe to plugin B's broadcasts.
* **No UI → background `socket.emit`.** Use HTTP via `ctx.server.registerRouter` instead.
* **Structured-cloneable payloads.** Functions, DOM nodes, and class instances with methods do not survive Electron IPC serialization.
