Render Jobs

Last Updated: 2026-07-21

A render takes about a minute and its Lambda runs fire-and-forget, so the web app needs somewhere to say what a render is doing. That is the RenderJob node. This page covers its status machine in apps/web/src/lib/neo4j/render-jobs.ts and the render helpers in apps/web/src/lib/flights/. The UI that starts these jobs is the Flight Deck; the model they render is the overlay pipeline.

The status machine

RenderJobStatus (in render-jobs.ts) is:

QUEUED → PROXYING → AWAITING_SYNC → RENDERING → ENCODING → COMPLETE | FAILED
  • COMPLETE and FAILED are terminal (isTerminal).
  • AWAITING_SYNC is the parked-on-a-human state (isParked): nothing is running and nothing is wrong — the job is waiting for the owner to scrub the video and mark the liftoff frame. setRenderJobLiftoff is the single guarded transition out of it (guarded on status = 'AWAITING_SYNC', which makes a double-click harmless).
  • The two middle states exist because a browser cannot decode a raw camera file (Motion JPEG in AVI): PROXYING builds a small H.264 scrub copy, and AWAITING_SYNC is the job sitting still until a human marks the frame. An upload the browser can decode skips straight to AWAITING_SYNC with no proxy.
  • ENCODING is a real status in the machine — the render's progress-reporting phase between RENDERING and COMPLETE. (The read path also runs a watchdog: a non-terminal, non-parked job untouched for 20 minutes is reported FAILED at read time, since a Lambda that was killed mid-run can't write its own failure.)

What the record carries

RenderJob keeps, in plain terms:

  • sourceKey — the uploaded source; proxyKey — the browser-playable scrub copy (once AWAITING_SYNC on the proxy path); outputKey — the finished video (once COMPLETE).
  • liftoffVideoTimeS — where liftoff sits in the source video, null until the user marks it.
  • units (metric | imperial) and the render selection: overlayVariantId, chunkId, and — only for a custom chunk — chunkT0 / chunkT1 (the named chunks resolve their window from phase math at render time, so they carry no scalars).
  • progress (0–100), a user-safe error when FAILED, requestedBy (the authorisation boundary — a job is only visible to its requester), and expiresAt (mirroring the +30-day S3 lifecycle).

The keys stay server-side: the client-facing shapes expose booleans (hasOutput / hasPlayable) and a human title, never a key it could presign. Reads return null/[] on no match; writes throw on failure — except fetchRenderJob, which is deliberately not wrapped in cache() because its whole purpose is to observe a value that changes during a request (polling).

Render helpers (apps/web/src/lib/flights/)

  • overlay-variants.ts — the app-side passthrough to the lib's overlay-variant catalogue (getOverlayVariant / listOverlayVariants), so call sites depend on one app path rather than reaching across the workspace boundary.
  • render-selection.ts — maps a picked (variant, named chunk) into the render-start fields (resolveRenderSelection). units is derived from the variant (the two full-band-* variants encode it), never an independent toggle. The v1 picker only emits a named chunk ({ id }); custom is API-only.
  • render-job-title.ts — pure "video × variant × chunk" label formatter (renderJobTitle), e.g. onboard.avi × Full band (metric) × Boost, composed from the source-key basename and the variant/chunk labels. Touches no keys or ids the client shouldn't see.
  • phase-markers.ts — derives a flight's phase-marker times (flightPhaseMarkerTimes) and the named chunks it can offer (availableChunkIds), using the same buildPhasePresets availability rule as the chart toolbar. NamedChunkId excludes custom.