The GCS half of a plugin runs inside a sandboxed iframe served by Mission Control. Same envelope shape as the agent half, different transport (postMessage instead of msgpack-over-Unix-socket), same capability re-resolution.Documentation Index
Fetch the complete documentation index at: https://docs.altnautica.com/llms.txt
Use this file to discover all available pages before exploring further.
Iframe sandbox
Every GCS plugin gets one iframe per host instance. The iframe is mounted with the strictest sandbox flags that still allow JavaScript and lazy-load:allow-same-originis not set. The iframe runs in a null origin. It cannot read host cookies, localStorage, or the host’s document.allow-top-navigationis not set. The plugin cannot redirect the parent window.allow-formsis not set. The plugin renders forms using its own React tree, not browser-native form submission.allow-popupsis not set. No new windows.
CSP
The host serves each plugin from/plugins/<id>/ with a per-plugin
Content Security Policy:
connect-src 'self' means a plugin cannot fetch from the public
internet. If your plugin needs an outbound HTTP call, route it
through the agent half with the network.outbound capability.
The GCS half must not be your network egress point.
postMessage RPC envelope
Same shape as the agent IPC envelope, just delivered viawindow.parent.postMessage:
window.parent with targetOrigin set to the
host origin. The host posts back to the iframe’s contentWindow
with targetOrigin set to the null origin ("*", justified
because the iframe is sandboxed and there is no privileged origin
to leak to).
The SDK hides this. You write:
version: 1, generates
the id, awaits the correlated response, and throws HostError on
error envelopes.
Capability tokens on the GCS side
Every privileged RPC carriescapability in the envelope. The
host bridge (running on the parent page) re-resolves the required
capability from method and args, then checks the granted set
for that plugin. The plugin cannot lie its way past a missing
grant; the host ignores the envelope’s capability field for
authorization.
The bridge mirrors the agent-side logic exactly so plugin authors
debug one model, not two.
The 12 named UI slots
| Slot id | Where it renders |
|---|---|
fc.tab | Tab inside the FC view (between built-in tabs). |
command.tab | Tab inside the Command view. |
planner.tab | Tab inside the Planner view. |
hardware.tab | Tab inside the Hardware view. |
sidebar.left | Collapsible panel on the left edge. |
sidebar.right | Collapsible panel on the right edge. |
status.bar | Item in the bottom status bar. |
video.overlay | Layer on top of the active video pane. |
notification.rail | Channel in the notification rail. |
settings.section | Section in the Settings page. |
drone.detail.tab | Per-drone tab inside the drone detail panel. |
telemetry.detail | Pane inside per-channel telemetry detail. |
@altnautica/plugin-sdk’s slot types.
A plugin contributes to a slot by declaring a panels[] entry in
the manifest:
order field, ties broken by install order.
Slot orchestrator
The host runs a slot orchestrator per slot id. It:- Reads the install set from Convex.
- For each enabled plugin, looks at its
contributes.panelslist. - Mounts an iframe per plugin (one iframe per plugin, not one per slot, so a plugin contributing to four slots renders the same bundle four times in four mount points but speaks to one IPC channel).
- Hands each iframe a slot-specific message on first mount with
the
slot.idandslot.props.
slot.id and renders the matching
component. A typical entry point:
Themeing
The host pushes atheme.changed event with a flat record of CSS
variables on mount and on every theme toggle. Apply them in the
plugin’s root:
Error envelopes
Same codes as the agent side:permission_denied, method_unknown,
schema_invalid, handler_unset, handler_error. The SDK throws
HostError whose code is the machine-readable id. Branch on
code, not on message.
Devtools
When the host runs in developer mode (?dev=1 on the URL or the
operator opt-in under Settings), the iframe’s CSP relaxes to allow
'unsafe-eval' for the React refresh runtime, and the slot bar
shows a debug overlay with the plugin id, the slot id, and a copy
of the last 20 RPC envelopes.