Skip to main content

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";