Flight Deck & Overlay Composer

Last Updated: 2026-07-21

This page covers the Flight Deck — the browsable console for a single flight — and the overlay composer, the workbench surface where an owner turns an on-board video into a burned-in telemetry clip. It is the UI half of the render pipeline; the model it drives is the overlay pipeline, and the jobs it starts are tracked by render jobs.

Route & server actions

The workbench flight route lives at apps/web/src/app/workbench/flights/[id]/page.tsx. Today that file is a thin redirect that survives only to honour old links — it resolves the flight's model, checks ownership, and forwards to the canonical rocket-scoped flight page (/workbench/rockets/{modelId}/flights/{id}), where the Flight Deck actually renders.

The pipeline mutations are Server Actions in the sibling overlay-actions.ts ('use server'). They cover the whole upload → proxy → sync → render flow: startVideoUploadAction / completeVideoUploadAction (multipart upload), startProxyAction / startSyncAction (create the RenderJob and, for raw camera files, kick off the proxy pass), startRenderAction (mark liftoff and start the burn-in), and the reuse-sync actions (findReusableSyncAction / startReuseSyncRenderAction). Every Lambda invoke is fire-and-forget (InvocationType: 'Event') — a Vercel function cannot sit for the ~1–2 minutes a proxy or render takes — so the client polls the RenderJob node for status instead.

The Deck components

The Deck lives in apps/web/src/components/flights/deck/; the top-level pieces (most re-exported via deck/index.ts, the rest — phase-presets, reuse-render-panel, upload-cta — imported from their own module files) are:

  • FlightDeck — the client shell. It owns a single playhead t (via useFlightTransport), a single chart registry, the per-source visibility state, and the one unit toggle, then threads them into the left rail and the 2×2 tile grid so every chart, the stat strip, and the synced video move together.
  • DeckHeader — the flown-configuration editor inputs and the unit toggle, threaded from the route page.
  • DeckRail — the left rail: the flight switcher, source checkboxes, and the phase presets.
  • DeckTiles — the 2×2 chart surface: [1] video + live band, [2] Altitude, [3] Velocity, [4] Attitude (tilt/gyro). It builds its series from the same datasets and <TelemetryChart> primitives the legacy dashboard uses.
  • MediaPanel — the "Media & renders" section below the grid. It branches on surface: the workbench view shows the Videos list, the overlay composer, and a Renders list (with the self-describing "video × variant × chunk" titles, progress bars, and downloads); the public view shows only finished renders as sign-in-free download rows.
  • VideoTile — tile 1: the clean, gauge-free clip presigned inline, with the live telemetry band beneath it. It owns no t of its own — play/seek flow through the transport.
  • OverlayBand — the live telemetry band, drawn with the same buildOverlayModelcomputeLayoutdrawOverlayFrame pipeline the Lambda burns in; only the playhead source differs (see the overlay pipeline).
  • StatStrip — the flight's summary numbers (max altitude, max velocity, max accel, …).
  • AttitudeTile — the tilt/gyro attitude view.
  • SourceSpecCard — one telemetry source's parsed spec (serial/firmware, deploy settings, sim params), parsed server-side from the source's meta.
  • PhasePresets (phase-presets) — the rail's Phases section: one row per phase preset, each zooming every deck chart to that range.
  • ReuseRenderPanel (reuse-render-panel) — re-render an already-uploaded, already-synced source with a new style/clip, without re-uploading, re-proxying, or re-scrubbing. Keyed on the source's real identity (its job's server-resolved sourceKey), never on a freshly-picked local file.
  • UploadCta (upload-cta) — the workbench-only "+ Upload video or telemetry" affordance, gated client-side so a public deck stays cacheable.
  • useFlightTransport (use-flight-transport) — the single source of playback time. It owns t (seconds since detected liftoff) and the play/pause clock, and works identically with a video element or with data alone.

The overlay composer

The composer lives in apps/web/src/components/workbench/overlay/:

  • OverlayPanel (overlay-panel) — the one-path flow: pick a file → pick a variant + chunk → mark liftoff → render → download. For a browser-decodable container (mp4/mov/webm) the sync wizard scrubs the local file the instant it's picked; for the raw AVI the camera writes, the file goes up first and the Lambda builds a proxy to scrub.
  • SyncWizard (sync-wizard) — scrub the video to mark the liftoff frame. That offset is the only bridge between flight time and source time.
  • VariantChunkPicker (variant-chunk-picker) — choose the (variant, chunk) to render. Each variant option shows a live, client-side preview; only the available named chunks are offered (full always, plus boost/apogee/descent when the flight's markers yield that phase).
  • useRenderJob (use-render-job) — polls the job for status.
  • useMultipartUpload (use-multipart-upload) — drives the multipart video upload.

The v1 picker emits named chunks only. custom is API-ready but has no UI (owner decision — see render-selection.ts / phase-markers.ts).

Previews are free; burn-in is not

Every variant preview is client-side and free: it renders from the flight's telemetry alone — no video, no Lambda — using the same overlay model the render uses, so what you pick is what you get. Burn-in happens only when a render is started against a real uploaded video: that is the one path that invokes the Lambda and produces a downloadable clip. The mechanics of "one renderer, three hosts — identical pixels" are on the overlay pipeline page.