AdditionalFieldsConfig
export type AdditionalFieldsConfig = Record<string, FieldDelivery>;
AircraftClass
export type AircraftClass = "widebody" | "narrowbody" | "regional";
AltitudeUnit
export type AltitudeUnit = "ft" | "m";
AutoStartConfirmPayload
export type AutoStartConfirmPayload = {
/** VA-issued confirmation message. */
confirmation: string;
/** Current sim variables for display. */
simVariables: Record<string, string>;
/** The flight plan built from the matched booking. */
flightPlan: FlightPlan;
/** Booking bid_id so the renderer can fetch the full booking. */
bidId: number;
};
BadgeProps
export type BadgeProps = React.HTMLAttributes<HTMLDivElement> &
VariantProps<typeof badgeVariants>;
BooleanSettingDef
export type BooleanSettingDef = PluginSettingBase & {
type: "boolean";
default?: boolean;
};
BounceData
export type BounceData = {
bounceNumber: number;
timestamp: number;
maxAltitudeAgl: number;
touchdownVerticalSpeed: number;
touchdownGForce: number;
durationMs: number;
};
ButtonProps
export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
};
CapturePoint
export type CapturePoint = {
timestamp: number;
altitude: number;
altitudeAgl: number;
verticalSpeed: number;
groundSpeed: number;
indicatedAirspeed: number;
latitude: number;
longitude: number;
pitch: number;
bank: number;
gForce: number;
heading: number;
glideslopeDeviation: number | null;
localizerDeviation: number | null;
flapsControl: number;
windDirection: number;
windSpeed: number;
totalFuelLbs: number;
/** Snapshot of data.flapsHandleIndex at the capture moment, null if the sim doesn't provide it. */
flapsHandleIndex: number | null;
};
ComboboxOption
export type ComboboxOption = {
value: string;
label: string;
};
ComboboxProps
export type ComboboxProps = {
options: ComboboxOption[];
value?: string;
onValueChange?: (value: string) => void;
/** Trigger text shown when nothing is selected. */
placeholder?: string;
/** Placeholder inside the search field. */
searchPlaceholder?: string;
/** Shown when the search filter matches nothing. */
emptyText?: string;
/** Render a loading row instead of the list (e.g. while options are fetched). */
loading?: boolean;
loadingText?: string;
disabled?: boolean;
triggerClassName?: string;
contentClassName?: string;
align?: "start" | "center" | "end";
onOpenChange?: (open: boolean) => void;
};
ConnectionState
export type ConnectionState =
| "connecting"
| "connected"
| "disconnected"
| "error";
CurrentFlight
export type CurrentFlight = {
flightPlan: FlightPlan;
status: FlightStatus;
vaTrackingId: string | null;
stratosFlightId: string | null;
startedAt: number | null;
pausedAt: number | null;
totalPausedMs: number;
elapsedMs: number;
timeCaptureMode: "block" | "air";
timeCaptureStartAt: number | null;
timeCaptureEndAt: number | null;
lastTickTime: number | null;
currentPhase: FlightPhase;
latitude: number;
longitude: number;
altitude: number;
groundSpeed: number;
indicatedAirspeed: number;
heading: number;
startingFuel: number;
/** Zero fuel weight (lbs), captured at the same phase as startingFuel. */
startingZfw: number;
currentFuel: number;
landingFuel: number | null;
/** Fuel burned on previously-completed legs of a multi-leg flight (refuel stops, helo repeated ops). Folded into the final fuel_used at submission. */
fuelUsedPriorLegs: number;
lastUpdateSentAt: number | null;
updatesSent: number;
updateIntervalMs: number;
comments: FlightComment[];
lastError: string | null;
};
DateSettingDef
export type DateSettingDef = PluginSettingBase & {
type: "date";
default?: string;
};
DistanceUnit
export type DistanceUnit = "nm" | "km" | "mi";
EventCategory
export enum EventCategory {
SYSTEM = "system",
PHASE = "phase",
CONTROLS = "controls",
ENGINE = "engine",
WARNING = "warning",
POSITION = "position",
COMMENT = "comment",
}
FieldDelivery
export type FieldDelivery = {
phases: string[];
submission: boolean;
};
FlightComment
export type FlightComment = {
id: string;
message: string;
timestamp: number;
phase: FlightPhase;
};
FlightData
export type FlightData = {
// Position & Navigation
latitude: number;
longitude: number;
altitude: number;
altitudeAgl: number;
altitudeAglGear: number;
groundSpeed: number;
indicatedAirspeed: number;
trueAirspeed: number;
verticalSpeed: number;
// Orientation
heading: number;
pitch: number;
bank: number;
// Flight Controls & Aircraft State
gearControl: boolean;
flapsControl: number;
flapsLeftPosition: number;
flapsRightPosition: number;
planeOnground: boolean;
// Engine Data
enginesCount: number;
engine1Firing: boolean;
engine2Firing: boolean;
engine3Firing: boolean;
engine4Firing: boolean;
engine1N1: number;
engine2N1: number;
engine3N1: number;
engine4N1: number;
engine1N2: number;
engine2N2: number;
engine3N2: number;
engine4N2: number;
engine1PctRpm: number;
engine2PctRpm: number;
engine3PctRpm: number;
engine4PctRpm: number;
// Composite engine state (shaft spinning above idle threshold)
engine1On: boolean;
engine2On: boolean;
engine3On: boolean;
engine4On: boolean;
// Fuel
fuelTotalQuantityWeight: number;
fuelUsed: number;
// Weather
windDirection: number;
windSpeed: number;
pressureQNH: number;
altimeterSettings: number;
// Time (Simulation)
clockHour: number;
clockMin: number;
clockSec: number;
zuluHour: number;
zuluMin: number;
zuluDayOfMonth: number;
zuluMonthOfYear: number;
zuluYear: number;
localDayOfMonth: number;
localMonthOfYear: number;
localYear: number;
// Aircraft Information
aircraftType: string;
aircraftIcao: string;
aircraftLivery: string;
aircraftEmptyWeight: number;
zeroWeightPlusPayload: number;
// Simulator State
simulatorVersion: string;
pauseFlag: boolean;
slewMode: boolean;
simulationRate: number;
/**
* Achieved time-compression multiplier to integrate for flight time, when it
* differs from the requested `simulationRate`. X-Plane sets this from
* `sim/time/sim_speed_actual_ogl` — the rate actually achieved after fps
* limiting and sub-19fps time dilation — so compressed flight time tracks the
* sim clock that really advanced, not the rate the pilot requested. Left
* undefined on sims with no achieved-rate source (MSFS/P3D/FSX), where the
* requested `simulationRate` is authoritative.
*/
simulationRateActual?: number;
isInMenu: boolean;
isXPlane: boolean;
isHelicopter: boolean;
// Warning States
stallWarning: boolean;
overspeedWarning: boolean;
crashed: boolean;
// Landing Analysis
gForceTouchDown: number;
landingRate: number;
touchdownNormalVelocity: number;
touchdownLatitude: number;
touchdownLongitude: number;
landingDistance: number;
// ILS Approach (optional until all sim clients populate them)
glideslopeDeviation?: number;
localizerDeviation?: number;
hasGlideslope?: boolean;
// Landing Systems (optional until all sim clients populate them)
reverser1Deployed?: boolean;
reverser2Deployed?: boolean;
autobrakePosition?: number;
speedbrakePosition?: number;
// New PIREP capture inputs (optional — populated by sim clients that support them)
/** Unlimited-fuel mode flag. MSFS: UNLIMITED FUEL SimVar. X-Plane: undefined (no clean dataref). */
unlimitedFuel?: boolean;
/** Detent index of the flap handle. MSFS: FLAPS HANDLE INDEX. X-Plane: derived from flaprqst × acf_flap_detents. */
flapsHandleIndex?: number;
// Navigation & Flight Plan
transponderFreq: string;
com1Freq: string;
com2Freq: string;
nav1Freq: string;
nav2Freq: string;
milesToGo: number;
cruiseAltitude: number;
// Flight Phase & Analysis
phase: FlightPhase;
phaseString: string;
// System timestamp (not simulation time)
timestamp: number;
// Update counter to force React re-renders
updateCounter?: number;
// Debug info from FlightStateManager (optional, for debug panel)
_debug?: FlightStateDebugInfo;
};
FlightDataSnapshot
export type FlightDataSnapshot = {
timestamp: number;
altitude: number;
altitudeAgl: number;
groundSpeed: number;
verticalSpeed: number;
heading: number;
latitude: number;
longitude: number;
planeOnground: boolean;
pitch: number;
engine1N1: number;
engine2N1: number;
engine3N1: number;
engine4N1: number;
};
FlightEventPayload
export type FlightEventPayload = {
event: FlightLogEvent;
timestamp: number;
};
FlightEventsSnapshot
export type FlightEventsSnapshot = {
events: FlightLogEvent[];
isTracking: boolean;
elapsedTime: number;
};
FlightLandingPayload
export type FlightLandingPayload = {
type: "touchdown" | "bounce" | "settled";
timestamp: number;
landingAnalysis?: LandingAnalysis;
bounceData?: BounceData;
};
FlightLogEvent
export type FlightLogEvent = {
eventId: string;
eventTimestamp: string;
eventElapsedTime: number;
eventCondition: string;
category: EventCategory;
message: string;
data?: Record<string, unknown>;
};
FlightManagerPayload
export type FlightManagerPayload = {
flight: CurrentFlight | null;
pendingRecovery: RecoverableFlight | null;
/** True when the flight was paused due to simulator disconnection. */
simDisconnected: boolean;
timestamp: number;
};
FlightPhase
export enum FlightPhase {
UNKNOWN = "unknown",
BOARDING = "boarding",
PUSH_BACK = "push_back",
TAXI = "taxi",
TAKE_OFF = "take_off",
REJECTED_TAKE_OFF = "rejected_take_off",
CLIMB = "climb",
CRUISE = "cruise",
DESCENT = "descent",
APPROACH = "approach",
FINAL = "final",
LANDED = "landed",
GO_AROUND = "go_around",
TAXI_IN = "taxi_in",
ARRIVED = "arrived",
DEBOARDING = "deboarding",
}
FlightPhasePayload
export type FlightPhasePayload = {
previousPhase: FlightPhase;
currentPhase: FlightPhase;
timestamp: number;
pendingTransition?: PendingPhaseTransition;
};
FlightPhaseSnapshot
export type FlightPhaseSnapshot = {
currentPhase: FlightPhase;
previousPhase: FlightPhase | null;
pendingTransition: PendingPhaseTransition | null;
trends: FlightTrends | null;
isTracking: boolean;
};
FlightPlan
export type FlightPlan = {
source: "va" | "simbrief" | "manual";
sourceId?: string;
callsign: string;
flightNumber?: string;
airlineCode?: string;
departureIcao: string;
arrivalIcao: string;
route: string[];
aircraftIcao: string;
aircraftName?: string;
cruiseAltitude?: number;
plannedDistance?: number;
estimatedFlightTime?: number;
departureCoords?: { lat: number; lon: number };
arrivalCoords?: { lat: number; lon: number };
};
FlightStateDebugInfo
export type FlightStateDebugInfo = {
trends: FlightTrends;
pendingTransition: {
phase: string;
startTime: number;
elapsedSeconds: number;
requiredDelay: number;
altDelta?: number;
altGate?: number;
} | null;
levelFlight: {
beganAt: number | null;
durationSeconds: number | null;
startAlt: number | null;
altDelta: number | null;
};
samples: {
timestamp: number;
altitude: number;
verticalSpeed: number;
groundSpeed: number;
}[];
detectedCruiseAlt: number;
};
FlightStatus
export type FlightStatus =
| "preflight"
| "starting"
| "active"
| "paused"
| "completing"
| "completed"
| "failed"
| "cancelled";
FlightTrends
export type FlightTrends = {
/** Average vertical speed over sample window (fpm) */
avgVerticalSpeed: number;
/** Average ground speed over sample window (knots) */
avgGroundSpeed: number;
/** Altitude change rate calculated from actual altitude difference (fpm) */
altitudeChangeRate: number;
/** True if consistently climbing (positive altitude trend) */
isClimbing: boolean;
/** True if consistently descending (negative altitude trend) */
isDescending: boolean;
/** True if relatively level flight */
isLevel: boolean;
/** True if accelerating on ground (potential takeoff roll) */
isAccelerating: boolean;
/** True if decelerating on ground (potential landing rollout) */
isDecelerating: boolean;
/** Highest N1 across all engines */
highestN1: number;
/** True if any engine has significant thrust */
hasThrust: boolean;
/** Number of samples in the window */
sampleCount: number;
/** Time span of samples in ms */
timeSpanMs: number;
/** True if we have enough samples for reliable trend detection */
hasReliableData: boolean;
};
GateCapture
export type GateCapture = {
/** Actual AGL at capture, may be a few ft above/below the nominal 50 ft */
altitudeAgl: number;
indicatedAirspeed: number;
verticalSpeed: number;
bank: number;
pitch: number;
groundSpeed: number;
timestamp: number;
};
HistoryReportEntry
export type HistoryReportEntry = {
timestamp: number;
elapsedTime: number;
latitude: number;
longitude: number;
altitude: number;
altitudeAgl: number;
heading: number;
groundSpeed: number;
indicatedAirspeed: number;
verticalSpeed: number;
pitch: number;
bank: number;
phase: FlightPhase;
planeOnground: boolean;
landingRate: number | null;
};
JsonSettingDef
export type JsonSettingDef = PluginSettingBase & {
type: "json";
default?: string;
};
LandingAnalysis
export type LandingAnalysis = {
landingRateFpm: number;
simulatorVsAtTouchdown: number;
touchdownTimestamp: number;
touchdownLatitude: number;
touchdownLongitude: number;
touchdownHeading: number;
touchdownGroundSpeed: number;
/** Indicated airspeed at touchdown (knots) — sourced from the touchdown CapturePoint. */
touchdownIas: number;
/** Flaps handle index at touchdown, null if the sim doesn't provide one. */
touchdownFlapsIndex: number | null;
touchdownPitch: number;
touchdownBank: number;
/**
* Scored touchdown vertical-load G. The peak of a framerate-independent,
* time-constant EMA of per-frame gForce over the contact second — not the
* raw spike. This is the value scoring reads.
*/
touchdownGForce: number;
/**
* Forensic raw peak gForce over the same contact window (un-smoothed). Kept
* for diagnostics; NOT used for scoring. Optional/null when no frame-buffer
* samples fell in the window (the scored value then came from the instantaneous
* touchdown-frame G and there is no raw window peak to report).
*/
touchdownGForceRaw?: number | null;
approachAltitude: number;
approachIas: number;
approachVs: number;
approachBank: number;
approachPitch: number;
approachTimestamp: number;
/**
* Snapshot at ~50 ft AGL on final, used for the stabilized-approach gate.
* Null when the capture was missed (e.g. very fast descent, sim disconnect
* right before touchdown, or capture armed below 50 ft).
*/
gateCapture: GateCapture | null;
descentTimeSeconds: number;
averageDescentRateFpm: number;
bounceCount: number;
bounces: BounceData[];
landingDistanceFt: number;
captureQuality: "excellent" | "good" | "fair" | "poor";
captureNotes: string[];
};
LandingAnalysisSnapshot
export type LandingAnalysisSnapshot = {
landingRate: number | null;
bounceCount: number;
touchdowns: BounceData[];
isOnGround: boolean;
approachData: CapturePoint | null;
landingAnalysis: LandingAnalysis | null;
};
LandingAnalyzerDebugState
export type LandingAnalyzerDebugState = {
isArmed: boolean;
hasCapturedApproach: boolean;
hasLanded: boolean;
isLandingSettled: boolean;
isBouncing: boolean;
bounceCount: number;
approachCapture: CapturePoint | null;
touchdownCapture: CapturePoint | null;
lastAirborneVs: number;
sampleCount: number;
samples: { timestamp: number; vs: number; agl: number; aglGear: number }[];
calculatedLandingRate: number | null;
landingAnalysis: LandingAnalysis | null;
};
LandingSample
export type LandingSample = {
timestamp: number;
onGround: boolean;
verticalSpeed: number;
altitudeAgl: number;
altitudeAglGear: number;
gForce: number;
pitch: number;
bank: number;
groundSpeed: number;
latitude: number;
longitude: number;
glideslopeDeviation: number | null;
localizerDeviation: number | null;
};
ListSettingDef
export type ListSettingDef = PluginSettingBase & {
type: "list";
default?: string;
options: PluginSettingOption[];
};
LogEntryPayload
export type LogEntryPayload = {
id: number;
timestamp: string;
level: "debug" | "info" | "warn" | "error";
category: string;
message: string;
data?: string;
};
LongtextSettingDef
export type LongtextSettingDef = PluginSettingBase & {
type: "longtext";
default?: string;
placeholder?: string;
};
NotificationPayload
export type NotificationPayload = {
type: "info" | "success" | "warning" | "error";
title: string;
message: string;
/** Auto-dismiss duration in ms. */
duration?: number;
/** Stable ID for deduplication — toasts with the same ID replace each other. */
id?: string;
};
NumberSettingDef
export type NumberSettingDef = PluginSettingBase & {
type: "number";
default?: number;
min?: number;
max?: number;
step?: number;
};
PendingPhaseTransition
export type PendingPhaseTransition = {
phase: FlightPhase;
elapsed: number;
required: number;
};
PluginAirline
export type PluginAirline = {
/** Airline ID as Stratos knows it */
id: string;
/** Origin of the VA's API (no trailing slash) */
baseUrl: string;
/** Bearer for this airline's API. Null if the VA session has expired. */
token: string | null;
};
PluginAirlineAccessor
export type PluginAirlineAccessor = {
/** Returns the current airline, or null if no VA session is active. */
getCurrent(): Promise<PluginAirline | null>;
/**
* Returns an axios instance pre-configured for this airline's API
* with auto-refresh on 401. Safe to cache for the lifetime of the
* background module.
*/
createClient(): PluginVaApiClient;
};
PluginAlertSound
export type PluginAlertSound =
| "alert"
| "warning"
| "error"
| "success"
| "chime";
PluginAuthAccessor
export type PluginAuthAccessor = {
/** Get the current auth token, or null if not authenticated */
getToken(): Promise<string | null>;
/** Check if the user is currently authenticated */
isAuthenticated(): Promise<boolean>;
};
PluginAuthor
export type PluginAuthor = {
/** Unique developer slug (e.g. "skyvex-software") */
id: string;
/** Display name (e.g. "Skyvex Software") */
name: string;
/** Contact email address (optional) */
contact?: string;
};
PluginAvailableSettings
export type PluginAvailableSettings = PluginSettingDefinition[];
PluginBackgroundModule
export type PluginBackgroundModule = {
/** Called during shell startup after auth is initialised */
onStart(ctx: PluginContext): Promise<void>;
/** Called during shell shutdown for graceful teardown */
onStop(ctx: PluginContext): Promise<void>;
};
PluginConfigStore
export type PluginConfigStore = {
get<T>(key: string): Promise<T | undefined>;
get<T>(key: string, defaultValue: T): Promise<T>;
set<T>(key: string, value: T): Promise<void>;
delete(key: string): Promise<void>;
getAll(): Promise<Record<string, unknown>>;
};
PluginContext
export type PluginContext = {
/** Scoped logger (prefixed with plugin ID) */
logger: PluginLogger;
/** Scoped config store (namespaced to plugin) */
config: PluginConfigStore;
/** IPC registration helper (auto-prefixed channels) */
ipc: PluginIPCRegistrar;
/** Read-only auth token accessor */
auth: PluginAuthAccessor;
/** Read-only accessor for the bound airline (id, base URL, VA bearer) */
airline: PluginAirlineAccessor;
/** Express server route registrar */
server: PluginServerRegistrar;
/** SQLite database accessor (files stored in plugin's data directory) */
database: PluginDatabaseAccessor;
/**
* Live flight state: reads, subscriptions, the authoritative flight-log
* writer, and start guards.
*/
flight: PluginFlightAccessor;
/** Toast + sound surface. */
notify: PluginNotifier;
/** Blocking, awaited message dialogs (alert/confirm). */
dialog: PluginDialogApi;
/** Blocking, awaited input prompts (text/number/select). */
prompt: PluginPromptApi;
};
PluginDatabaseAccessor
export type PluginDatabaseAccessor = {
/**
* Open (or create) a SQLite database file scoped to this plugin's data directory.
* Returns a better-sqlite3 Database instance.
* @param filename - Name of the database file (e.g. "landing-reports.db")
*/
open(filename: string): unknown;
};
PluginDialogAlertInput
export type PluginDialogAlertInput = {
title: string;
message: string;
/** Acknowledge-button label. Default "OK". */
okLabel?: string;
/** Optional auto-cancel; on expiry the call resolves (void). */
timeoutMs?: number;
};
PluginDialogApi
export type PluginDialogApi = {
/** Show a blocking message with a single acknowledge button. */
alert(input: PluginDialogAlertInput): Promise<void>;
/** Ask a yes/no question. Resolves false on cancel/dismiss/timeout. */
confirm(input: PluginDialogConfirmInput): Promise<boolean>;
};
PluginDialogClosePayload
export type PluginDialogClosePayload = {
requestId: string;
};
PluginDialogConfirmInput
export type PluginDialogConfirmInput = {
title: string;
message: string;
/** Confirm-button label. Default "Confirm". */
confirmLabel?: string;
/** Cancel-button label. Default "Cancel". */
cancelLabel?: string;
/** Optional auto-cancel; on expiry the call resolves false. */
timeoutMs?: number;
};
PluginDialogKind
export type PluginDialogKind =
| "alert"
| "confirm"
| "text"
| "number"
| "select";
PluginDialogResult
export type PluginDialogResult = {
submitted: boolean;
value?: string;
};
PluginDialogSelectOption
export type PluginDialogSelectOption = {
value: string;
label: string;
};
PluginDialogSpec
export type PluginDialogSpec = {
requestId: string;
pluginId: string;
pluginName: string;
kind: PluginDialogKind;
title: string;
/** Body text (dialogs) or helper text above the field (prompts). */
message?: string;
/** Field label (prompts only). */
label?: string;
placeholder?: string;
/** Pre-filled value (always a string on the wire; number prompts stringify it). */
defaultValue?: string;
required?: boolean;
maxLength?: number;
min?: number;
max?: number;
step?: number;
options?: PluginDialogSelectOption[];
okLabel?: string;
confirmLabel?: string;
cancelLabel?: string;
submitLabel?: string;
timeoutMs?: number;
};
PluginFlightAccessor
export type PluginFlightAccessor = {
/** Current flight-manager state (flight, pending recovery, sim-disconnect). */
getState(): Promise<FlightManagerPayload>;
/** Latest simulator data snapshot, or null when no sim is connected. */
getSnapshot(): Promise<SimDataSnapshot | null>;
/** Current flight-phase snapshot. */
getPhase(): Promise<FlightPhaseSnapshot>;
/** Current landing-analysis snapshot. */
getLandingReport(): Promise<LandingAnalysisSnapshot>;
/** Subscribe to flight-manager state changes. Returns an unsubscribe. */
onUpdate(cb: (state: FlightManagerPayload) => void): () => void;
/** Subscribe to flight-phase changes. Returns an unsubscribe. */
onPhaseChange(cb: (payload: FlightPhasePayload) => void): () => void;
/** Subscribe to live simulator data frames. Returns an unsubscribe. */
onSimData(cb: (snapshot: SimDataSnapshot) => void): () => void;
/** Subscribe to landing events (touchdown/bounce/settled). Returns an unsubscribe. */
onLanding(cb: (payload: FlightLandingPayload) => void): () => void;
/** Authoritative flight-log writer. */
log: PluginFlightLogWriter;
/**
* Register a guard consulted inside `startFlight` BEFORE the flight is
* created or persisted. A guard returning `{ allow: false, reason }` vetoes
* the start (surfaced via the existing preflight-failure path). Guards are
* fail-open: a throw or timeout (~5s) allows the start with a loud warning.
* Multiple guards: the first `allow: false` wins. Returns an unsubscribe.
*/
registerStartGuard(guard: PluginStartGuard): () => void;
};
PluginFlightLogInput
export type PluginFlightLogInput = {
category: EventCategory;
condition: string;
message: string;
data?: Record<string, unknown>;
};
PluginFlightLogPatch
export type PluginFlightLogPatch = Partial<
Pick<PluginFlightLogInput, "category" | "message" | "data">
>;
PluginFlightLogWriter
export type PluginFlightLogWriter = {
/**
* Append an event to the flight log. Returns the created event, or null when
* there is no active tracking session (mirrors `addComment`).
*/
add(event: PluginFlightLogInput): Promise<FlightLogEvent | null>;
/** Remove a previously-added event by id. Returns true if it existed. */
remove(eventId: string): Promise<boolean>;
/** Patch an existing event in place. Returns true if it existed. */
update(eventId: string, patch: PluginFlightLogPatch): Promise<boolean>;
};
PluginIPCRegistrar
export type PluginIPCRegistrar = {
/** Register a handler for an IPC channel. Channel is auto-prefixed. */
handle(channel: string, handler: (...args: unknown[]) => unknown): void;
/** Remove a handler for an IPC channel. */
removeHandler(channel: string): void;
/** Send a message to the renderer. Channel is auto-prefixed. */
send(channel: string, ...args: unknown[]): void;
};
PluginLogger
export type PluginLogger = {
info(category: string, message: string, ...args: unknown[]): void;
warn(category: string, message: string, ...args: unknown[]): void;
error(category: string, message: string, ...args: unknown[]): void;
debug(category: string, message: string, ...args: unknown[]): void;
};
PluginManifest
export type PluginManifest = {
/** Unique plugin slug (e.g. "flight-tracking") */
id: string;
/** Plugin type — "user" for plugins installed by the user, "airline" for plugins installed and managed by the VA on the user's behalf */
type: "user" | "airline";
/** Human-readable display name */
name: string;
/** Semantic version (e.g. "1.0.0") */
version: string;
/** Short description of the plugin's purpose */
description: string;
/** Plugin author information */
author: PluginAuthor;
/** Sidebar icon for light theme — relative path to a PNG in the plugin's assets/ directory (e.g. "icon-light.png") */
icon_light: string;
/** Sidebar icon for dark theme — relative path to a PNG in the plugin's assets/ directory (e.g. "icon-dark.png") */
icon_dark: string;
/** Optional homepage URL for the plugin (e.g. "https://www.example.com/"). Rendered as a clickable link in the plugin info panel. Must be a fully-qualified http(s) URL. */
homepage?: string;
/** Typed settings this plugin declares. User-scoped settings render in the Settings page; airline-scoped settings are managed by the VA platform. */
availableSettings?: PluginSettingDefinition[];
/** Whether to include source maps in the published CDN bundle. Defaults to false — source maps are stripped on approval. Set to true if you want end users to have access to source maps for debugging. */
includeSourceMaps?: boolean;
};
PluginNavigationHelper
export type PluginNavigationHelper = {
/** Navigate to a route within this plugin */
navigateTo(path: string): void;
/** Navigate to a route in another plugin */
navigateToPlugin(pluginId: string, path: string): void;
/** Navigate to a shell route */
navigateToShell(path: string): void;
/** Get the current route path */
getCurrentPath(): string;
};
PluginNotifier
export type PluginNotifier = {
/** Show a toast in the renderer. */
toast(n: PluginToastInput): Promise<void>;
/** Play a shell-bundled sound by name (no plugin-supplied audio). */
sound(name: PluginAlertSound): Promise<void>;
};
PluginPilotUser
export type PluginPilotUser = {
dbID: number;
pilotID: string;
firstName: string;
lastName: string;
email: string;
rank: string;
rankLevel: number;
rankImage: string;
avatar: string;
};
PluginPromptApi
export type PluginPromptApi = {
/** Ask for free text. */
text(input: PluginPromptTextInput): Promise<string | null>;
/** Ask for a number. A non-numeric/blank submit resolves null. */
number(input: PluginPromptNumberInput): Promise<number | null>;
/** Ask the pilot to pick one of the supplied options. Returns the value. */
select(input: PluginPromptSelectInput): Promise<string | null>;
};
PluginPromptNumberInput
export type PluginPromptNumberInput = {
title: string;
label: string;
message?: string;
placeholder?: string;
defaultValue?: number;
required?: boolean;
min?: number;
max?: number;
step?: number;
submitLabel?: string;
cancelLabel?: string;
timeoutMs?: number;
};
PluginPromptSelectInput
export type PluginPromptSelectInput = {
title: string;
label: string;
message?: string;
options: { value: string; label: string }[];
/** Pre-selected option value. */
defaultValue?: string;
required?: boolean;
submitLabel?: string;
cancelLabel?: string;
timeoutMs?: number;
};
PluginPromptTextInput
export type PluginPromptTextInput = {
title: string;
/** Field label, e.g. "What gate are you departing from?". */
label: string;
/** Optional helper/body text shown above the field. */
message?: string;
placeholder?: string;
defaultValue?: string;
/** When true, Submit is disabled until the field is non-empty. */
required?: boolean;
maxLength?: number;
submitLabel?: string;
cancelLabel?: string;
timeoutMs?: number;
};
PluginRouteComponent
export type PluginRouteComponent =
| ComponentType
| LazyExoticComponent<ComponentType>;
PluginServerRegistrar
export type PluginServerRegistrar = {
/** Register an Express router at the given path prefix (e.g. '/api/booking') */
registerRouter(prefix: string, router: unknown): void;
};
PluginSettingDefinition
export type PluginSettingDefinition =
| BooleanSettingDef
| TextSettingDef
| LongtextSettingDef
| NumberSettingDef
| RangeSettingDef
| ListSettingDef
| RadioSettingDef
| DateSettingDef
| JsonSettingDef;
PluginSettingOption
export type PluginSettingOption = {
value: string;
label: string;
};
PluginSettingType
export type PluginSettingType =
| "boolean"
| "text"
| "longtext"
| "number"
| "range"
| "list"
| "radio"
| "date"
| "json";
PluginStartContext
export type PluginStartContext = {
plan: FlightPlan;
snapshot: SimDataSnapshot | null;
isAutoStart: boolean;
};
PluginStartDecision
export type PluginStartDecision =
| { allow: true }
| { allow: false; reason: string };
PluginStartGuard
export type PluginStartGuard = (
ctx: PluginStartContext,
) => PluginStartDecision | Promise<PluginStartDecision>;
PluginToastAPI
export type PluginToastAPI = {
success(message: string): void;
error(message: string): void;
info(message: string): void;
warning(message: string): void;
};
PluginToastInput
export type PluginToastInput = {
type: "info" | "success" | "warning" | "error";
title: string;
message: string;
durationMs?: number;
id?: string;
};
PluginUIContext
export type PluginUIContext = {
/**
* Shared axios instance for the bound airline's API, with auto-refresh on
* 401. Provided by the shell so all plugin UIs and the shell itself share
* one in-flight refresh promise — concurrent 401s coalesce to a single
* `POST /api/auth/va/refresh`. Reach this through `useVaApi()`.
*/
vaApi: AxiosInstance;
/** The plugin's unique ID */
pluginId: string;
/** Auth state from the shell */
auth: {
isAuthenticated: boolean;
token: string | null;
user: PluginPilotUser | null;
};
/** Current airline info (from Stratos API). Bundled with the VA bearer
* so plugins can call the airline backend directly. */
airline?: {
id: string;
name: string;
icao: string;
logo_light: string;
logo_dark: string;
/** Origin of the VA's API (no trailing slash). */
base_url: string;
/** Bearer for this airline's API. Null if the VA session has expired. */
token: string | null;
} | null;
/** Config access hooks */
config: {
get<T>(key: string): T | undefined;
get<T>(key: string, defaultValue: T): T;
};
/**
* Real-time bridge between this plugin's background and UI modules.
*
* - `on(event, handler)` subscribes to broadcasts the background made via
* `ctx.ipc.send(event, payload)`. The shell scopes the channel to the
* current plugin id automatically — pass the leaf event name on both
* ends. Plugin A's UI cannot receive plugin B's broadcasts.
* - `off(event, handler)` removes a previously registered handler.
* - `emit` is intentionally not implemented. Send UI→background traffic
* through HTTP routes registered via `ctx.server.registerRouter`.
* - `connected` is always `true` in the renderer; the underlying Electron
* IPC pipe has no connection lifecycle.
*/
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 utilities */
navigation: PluginNavigationHelper;
/** Toast/notification API */
toast: PluginToastAPI;
/** Scoped logger for renderer-side logging */
logger: PluginLogger;
};
PluginUIModule
export type PluginUIModule = PluginRouteComponent;
PluginVaApiClient
export type PluginVaApiClient = AxiosInstance;
PreflightCheck
export type PreflightCheck = {
name: string;
passed: boolean;
message: string;
};
PreflightCheckResult
export type PreflightCheckResult = {
passed: boolean;
checks: PreflightCheck[];
};
ProtocolUrlPayload
export type ProtocolUrlPayload = {
/** The action segment of the URL (e.g. "connect", "open"). */
action: string;
/** Parsed query / path parameters. */
params: Record<string, string>;
/** The original full URL string. */
fullUrl: string;
};
RadioSettingDef
export type RadioSettingDef = PluginSettingBase & {
type: "radio";
default?: string;
options: PluginSettingOption[];
};
RangeSettingDef
export type RangeSettingDef = PluginSettingBase & {
type: "range";
default?: number;
min: number;
max: number;
step?: number;
};
RecoverableFlight
export type RecoverableFlight = {
id: string;
callsign: string | null;
departureIcao: string | null;
arrivalIcao: string | null;
aircraftIcao: string | null;
latitude: number | null;
longitude: number | null;
altitude: number | null;
heading: number | null;
groundSpeed: number | null;
indicatedAirspeed: number | null;
startedAt: number | null;
elapsedMs: number;
status: string;
phase: string | null;
/** Last known fuel in lbs (canonical). Null if no fuel data was captured. */
lastFuel: number | null;
};
SimDataSnapshot
export type SimDataSnapshot = {
data: FlightData | null;
isConnected: boolean;
simulatorType: string | null;
};
SimulatorDataPayload
export type SimulatorDataPayload = {
/** Identifier of the connected simulator (e.g. "xplane", "msfs"). */
simulatorType: string;
/** Full flight data snapshot from the simulator. */
data: FlightData;
/** Wall-clock timestamp (epoch ms) when the data was emitted. */
timestamp: number;
/** Computed trend analysis from the rolling sample window. */
trends: FlightTrends;
};
SimulatorStatus
export type SimulatorStatus =
| "disconnected"
| "connecting"
| "connected"
| "error";
SimulatorStatusPayload
export type SimulatorStatusPayload = {
/** Identifier of the simulator. */
simulatorType: string;
/** New connection status. */
status: SimulatorStatus;
/** Optional human-readable message (e.g. error details). */
message?: string;
};
SimulatorType
export enum SimulatorType {
XPLANE = "xplane",
MSFS = "msfs",
FSX = "fsx",
P3D = "p3d",
UNKNOWN = "unknown",
}
SocketEventName
export type SocketEventName =
(typeof SOCKET_EVENTS)[keyof typeof SOCKET_EVENTS];
StartFlightOptions
export type StartFlightOptions = {
/**
* Start even when preflight checks fail — set when the pilot chooses "Start
* Anyway" after a preflight error. Also overrides plugin start guards.
*/
forceStart?: boolean;
/** True when the start was triggered by auto-start (not a manual click). Surfaced to plugin start guards. */
isAutoStart?: boolean;
};
StartFlightResult
export type StartFlightResult = {
success: boolean;
flight?: CurrentFlight;
preflightResult?: PreflightCheckResult;
/** VA-issued confirmation message — pilot must acknowledge before tracking starts */
vaConfirmation?: string;
/** Sim variables sent to VA for display in confirmation dialog */
simVariables?: Record<string, string>;
error?: string;
};
SystemMetricsPayload
export type SystemMetricsPayload = {
cpu: {
/** System-wide CPU percentage. */
percentage: number;
/** This process's CPU percentage. */
process: number;
};
memory: {
/** System used memory (bytes). */
used: number;
/** System total memory (bytes). */
total: number;
/** Process RSS (bytes). */
process: number;
/** Process heap used (bytes). */
processHeap: number;
};
/** Wall-clock timestamp (epoch ms). */
timestamp: number;
};
TextSettingDef
export type TextSettingDef = PluginSettingBase & {
type: "text";
default?: string;
pattern?: string;
placeholder?: string;
};
ThemeMode
export type ThemeMode = "dark" | "light" | "system";
UnitPreferences
export type UnitPreferences = {
weight: WeightUnit;
altitude: AltitudeUnit;
distance: DistanceUnit;
};
WeightUnit
export type WeightUnit = "lbs" | "kg";