Changelog
The most recent Airwave releases and what changed in each.
The 20 most recent releases. For the complete history (874 releases), see CHANGELOG.md on GitHub.
0.16.10 (2026-10-23)
Internal
- Node baseline moved from 22 to 24 (current active LTS): the Docker base image
(
node:24-bookworm-slim) and thedesktop/tv-tauriCI workflows. The server runs on Bun and is unaffected; this is build tooling only, with no change to the shipped runtime. Verified locally on Node 24.22.1: clean install,check-fastacross all 16 workspaces, the web/tv-web vite builds, the server build, and the Expo CLI all pass.
0.16.9 (2026-10-21)
Internal
- macOS release build retries
stapler staple. Apple'snotarytool --waitreturns "Accepted" before the notarization ticket is reliably published to its CloudKit ticket-delivery service, so an immediatexcrun stapler staplecan fail to fetch it (NSURLErrorDomain-1005, Error 68) and fail the desktop release job even though notarization itself succeeded (seen on the macOS Intel runner during the v0.16.8 release). The signing script now retries the staple with backoff (up to ~5 minutes), so a transient ticket-delivery flake no longer breaks the build. CI-only; no change to the shipped app.
0.16.8 (2026-10-21)
Security: the SSE endpoint no longer accepts the API key in the URL at all (completes GHSA-fjx9-jwmr-2r58).
Changed
GET /api/public/v1/eventsno longer accepts?api_key=. The key is accepted only via theX-API-Key/Authorization: Bearerheader (fetch-based clients) or exchanged for a single-use?ticket=(browserEventSource, viaPOST /events/ticket, added in 0.16.7). This removes the last path that put a long-lived credential in a URL — and thus in proxy/CDN access logs and browser history.
Breaking / Migration
- A browser
EventSourcethat connected with?api_key=<key>must switch to the ticket flow:POST /api/public/v1/events/ticketwith the key in theX-API-Keyheader, then openGET /api/public/v1/events?ticket=<ticket>. Because a ticket is single-use, reconnect by minting a fresh one. Fetch-based clients (Tauri, dashboards, Home Assistant) are unaffected — they already use the header. Docs updated at/docs/integrations/tauri.
0.16.7 (2026-10-21)
Security follow-up to 0.16.6: a single-use connection ticket for the SSE stream, so the API key need not ride in the URL at all (GHSA-fjx9-jwmr-2r58).
Added
POST /api/public/v1/events/ticketmints a single-use, ~60-second connection ticket (header-authed). A browserEventSourcecan't send an auth header, so it now exchanges its key for a ticket and opens the stream with?ticket=— the long-lived key never travels in a URL, so it can't leak via proxy/CDN access logs or browser history. The stream (GET /api/public/v1/events) accepts?ticket=alongside the existing header and?api_key=auth (both kept for compatibility). Tickets are in-memory, single-use, and expire in ~60s, so a leaked ticket is dead on arrival.
Dev
scripts/sse-listen-ticket.ts— an SSE listener that exercises the ticket path (mint →?ticket=), the browser-style counterpart tosse-listen.ts(which tests the header/key path).
0.16.6 (2026-10-21)
Security: stops the public API key (and other secrets) from being written to logs (GHSA-fjx9-jwmr-2r58).
Fixed
- Secrets passed in a URL are now redacted from the request log. The SSE endpoint accepts the key as
?api_key=(a browserEventSourcecan't set headers), and Hono's request logger writes the full path including the query string — so the key was landing in stdout /docker logsin plaintext. The logger now masksapi_key/token/X-Plex-Token/code/state/ticketquery values before printing.?api_key=still works unchanged; only the log output is affected. (A short-lived connection ticket for SSE, so the key need not ride in the URL at all, follows next.) Reported by @cashcon57.
0.16.5 (2026-10-21)
Security: closes an open redirect in the Plex sign-in flow (GHSA-6p5v-m89f-x7hg).
Fixed
- Plex web sign-in now validates the
redirect_uri./api/plex/authorizebuilt Plex'sforwardUrlfrom an unvalidatedredirect_uri, so a crafted sign-in link could bounce the Plex pin to an attacker's URL after the victim approved on plex.tv. The proxy now accepts aredirect_urionly when its path is the better-auth Plex callback AND its origin is trusted — the configuredBETTER_AUTH_URL/CORS_ORIGIN/EXTRA_CORS_ORIGINS, or the forwarded host when the request arrived over the Airwave Cloud tunnel — so the pin can only ever return to your own callback. Every real sign-in path (localhost, reverse proxy, LAN, tunnel) is unaffected. Reported by @cashcon57.
0.16.4 (2026-10-21)
Security: closes an unauthenticated path-traversal in the /img artwork proxy (GHSA-vc4j-53mw-85mx).
Fixed
- The
/imgartwork proxy can no longer be tricked into reaching arbitrary Plex endpoints. It took a caller-supplied path and checked only that it started with/libraryor/photo, but../%2e%2esegments are resolved byfetchAFTER that check — so a crafted?path=could reach any admin-token Plex endpoint (sessions, preferences, library/account data) and the/phototranscoder'surl=(SSRF), unauthenticated and reachable over Remote Access. Artwork paths are now resolved first and must stay on the same Plex server under/library/, with no traversal, host change, or query string;w/hare clamped to a sane range; the public artwork route additionally constrains the rating key to digits. No change to legitimate artwork (every real art path is/library/metadata/...). Reported by @cashcon57.
0.16.3 (2026-10-15)
Resolves apps/desktop's electrobun type errors, so pnpm check-fast is green across the whole repo.
Internal
apps/desktopresolveselectrobun/electrobun/bunto the Hutch devkit for types. electrobun v2 ships its SDK through the Hutch devkit (.hutch/, gitignored, projected byelectrobun dev), not npm — theelectrobunnpm package is a stub that throws on import, so TypeScript fell back to it and reportedTS7016(no declarations) and a knock-onTS7006(implicit-any callback param). Apathsmapping inapps/desktop/tsconfig.jsonpoints the two imported specifiers at the devkit's real types. It's inline rather thanextends .hutch/devkit/tsconfig.json, so the config stays valid when the devkit is absent (fresh checkout, or the 1.18.1 macOS-Intel build job that uses the classic CLI, no Hutch). No build impact — electrobun resolves its SDK through Hutch, not this tsconfig.
0.16.2 (2026-10-15)
Removes vestigial TypeScript declaration-emit config so the server and internal packages type-check cleanly. No behavior change.
Internal
apps/serverandpackages/{api,auth,db}no longer setcomposite/declaration/outDir. These packages are consumed as source (theirexportspoint at./src, and the server is bundled by tsdown, nottsc), so the project-reference and declaration-emit options were dead config — yet they switched on tsc's declaration-portability (TS2883) and rootDir (TS6059/TS6307) checks, leaving ~133 phantom errors thatbun check(and a forcedtsc) surfaced. The internal packages now use the{ "noEmit": true }baseline; the server keeps itspaths/jsxand itscheck-typesmoves fromtsc -btotsc --noEmit.pnpm check-fastis now green across the repo exceptapps/desktop(tracked separately). The Docker build uses tsdown and is unaffected.
0.16.1 (2026-10-15)
Adds check-fast, a fast bun check typecheck across the monorepo.
Internal
pnpm check-fastruns Bun's TypeScript type checker (bun check, TS7, roughly 6x faster than tsc) across every workspace, andpnpm -F <pkg> check-fastchecks a single package. Each workspace gains acheck-fastscript; web/tv-web runvite buildfirst to generate their (gitignored) route tree. Namedcheck-fastrather thancheckbecausebun checkruns a same-named script if one exists, which would recurse. The existing tsccheck-typesis unchanged; this is purely additive.
0.16.0 (2026-10-15)
Type-correctness pass: fixes the real latent type errors in shipped code and holds the server's diagnostic scripts to the project's strict typing. No behavior change.
Fixed
- GUID remap resolver (
guid-remap.ts) is now type-proven. Its array indexing relied on length/empty guards that were already runtime-correct; the code now satisfiesnoUncheckedIndexedAccesstoo (guarded the resolved row and the live-match candidate, and narrowed the guid-scheme filter to its real union). Behavior is unchanged. - Remote-access relay buffers type as
Uint8Array<ArrayBuffer>.encodeBody/sendnow carry the ArrayBuffer-backed buffer type the browserWebSocket.sendrequires. - AI-lineup replay view: removed a dead
onScrubprop from the gantt scrubber (its slider is currently disabled and the prop was never passed). - Workflow engine: cast the
Worldvalue across the@workflow/world-postgres↔workflow/runtimetype skew (the queue-name brand), matching the existing workaround in the same file.
Internal
- Server diagnostic scripts (
apps/server/scripts/*) held to strict typing (removed unused bindings, guarded possibly-undefined indexing, loosened a probe helper to acceptnullids), since the server tsconfig includesscripts/.
0.15.66 (2026-10-13)
Fixes the Apple TV dropping back to server setup after a restart (issue #64).
Fixed
- Apple TV now keeps its server and session across a reboot. On tvOS, the server URL, token, and session were stored in AsyncStorage, which the OS keeps in a purgeable cache and clears when storage runs low — so after a restart the Apple TV had forgotten its server and returned to onboarding. Those small values now live in UserDefaults (via React Native's built-in
Settings), the one tvOS store with a durability guarantee, so they survive a reboot. No new native module or dependency; iPad, iPhone, and Android are unchanged (they keep the Keychain/Keystore and AsyncStorage). A one-time migration carries over any values that haven't been purged yet; an Apple TV whose cache was already cleared re-onboards once.
Internal
session-storage.tsbacks the Apple-TV durable store with core RNSettings(NSUserDefaults), gated onPlatform.OS === "ios" && Platform.isTV, using an empty-string tombstone for deletes (UserDefaults has no remove).
0.15.65 (2026-10-12)
Fixed
- Cloning a membership or manual channel now loads its preview automatically. Cloning pre-fills the create page with the source channel's setup (all three modes already worked). But the Preview only auto-resolved for filter clones; a playlist/collection or hand-picked clone sat on "Nothing to preview yet" until you nudged it. The preview's "load when the pool goes from empty to populated" detection now fires on the create/clone page for every mode, so a clone's pre-filled pool previews on load. (The edit page is unchanged — it still shows the saved preview without a redundant re-resolve.)
0.15.64 (2026-10-12)
Fixed
- Re-importing a lineup now detects hand-picked and collection/playlist channels as "already imported." Filter channels were correctly flagged as duplicates (and skipped) on a re-import, but manual and membership channels weren't — they showed up ready to import again even though they already existed. The duplicate check now includes a manual channel's picked items and a membership channel's collections/playlists in its content signature, so re-importing the same lineup onto the same server flags all of them as already imported and leaves them unchecked by default.
0.15.63 (2026-10-12)
Added
- Export a single channel or package, not just the whole lineup.
exportLineuptakes an optional selection (channel numbers or package keys) and produces the same versioned document with just those entries (a single-channel export brings its package along), via a newtransfer.exportSelectionendpoint. Groundwork for sharing one channel/package.
Internal
- Documented that the channel-editor filter Copy/Paste envelope (
airwave/filter, client-only, filter-only) stays deliberately separate from the server-side lineup-exchange format — different payload, transport, and scope, so they aren't unified.
0.15.62 (2026-10-12)
Importing a lineup now recreates hand-picked and collection/playlist channels instead of dropping them.
Added
- Manual and membership channels import for real. Previously only filter channels survived an import; hand-picked (manual) and collection/playlist (membership) channels were dropped. Now:
- Same Plex server (re-importing onto the box the lineup came from): the channel imports exactly as-is — a membership channel stays a live collection/playlist, a manual channel keeps its picks. No data lost.
- Different server: the channel's items are matched to your library by their metadata ids (tmdb/tvdb/imdb) — a fast indexed lookup, with a live Plex fallback for anything not yet synced — and rebuilt as a hand-picked snapshot. The import reports "matched X of Y titles"; unmatched titles are skipped, never a hard failure.
- Staging preview shows portability. A manual/membership channel from another server now shows how many of its titles match your library before you import.
Internal
- Carries the source Plex server id in the export (schema 1.2) to detect the same-server case; the import plan resolves each definition (predicate / same-server membership+manual / cross-server remap) and
executeChannelPlanperforms the GUID remap. The content signature now includes manual/membership item identity so distinct hand-picked channels don't collide. Verified with a dry-run round-trip probe.
0.15.61 (2026-10-12)
Exports now carry portable identity for hand-picked and collection/playlist channels (schema 1.2).
Added
- Manual and membership channels export their items by external GUID. A hand-picked (MANUAL) channel exports each pick's tmdb/tvdb/imdb id (a whole-show pick stays a show, so it keeps expanding live after import); a collection/playlist (MEMBERSHIP) channel exports its container's direct members by GUID (shows stay shows, not exploded into episodes). This is what lets these channels rebuild against a different server's library on import. Bumps the exchange format to schema 1.2 (additive).
- Export-time GUID backfill. If a referenced item's id isn't cached yet (e.g. exported before a metadata re-sync), the exporter fetches it live from Plex on the fly and persists it, so an export is always complete regardless of sync state. Bounded to only the referenced items missing an id.
0.15.60 (2026-10-12)
The engine that resolves a shared channel's items to your own library (for cross-server import).
Added
- GUID → ratingKey remap resolver (
services/transfer/guid-remap.ts): given a shared item's external ids (tmdb/tvdb/imdb), find the matching item in this server's library. Tiered for speed and completeness: a single indexed lookup over the newMediaItemid columns resolves everything that's synced; anything missing (added since the last sync, or in an unsynced library) falls back to a liveGET /library/matchescall to Plex, and the ids it finds are written back to the cache so the next import is a pure indexed hit. Reports matched vs. unmatched; an unmatched item is skipped with a note, never a failure. Not wired into import yet — that's next. matchByGuidPlex client helper wrapping/library/matches(external guid → local ratingKey, score-gated for movies/shows), andincludeGuids=1on the batched metadata fetch.
Dev
probe-guid-remap.tsexercises the resolver end to end against the real library.
0.15.59 (2026-10-12)
Makes the external metadata ids first-class, indexed columns on cached media (for cross-server channel import).
Added
MediaItem.tmdbId/tvdbId/imdbIdcolumns (migrationadd_mediaitem_external_ids), each indexed per source, so a shared channel's items can be resolved to this server'sratingKeyby a fast indexed lookup. The ids move out of theguideJSON blob (where 0.15.55 first stashed them) onto the item itself, since they're identity, not display metadata. Populated by the metadata sync.
Note
- The columns start empty and fill in on the next metadata sync (no backfill — a re-sync self-repairs). Nothing reads them yet; the import resolver that uses them is next.
Dev
probe-external-ids.tsnow reports column coverage;probe-guid-match.tssamples ground truth from the columns.
0.15.58 (2026-10-12)
Lineup export/import stops dropping four channel settings (schema 1.1).
Fixed
- Exporting a channel now carries its scheduling config. The export predated several
Channelcolumns and silently dropped them, so an imported channel lost its grouping/rotation strategy, its Exclude specials and Keep multi-part together toggles, and its shuffle seed (so it re-shuffled differently). All four now travel with the channel and are applied on import, so a round-tripped channel matches the original. Correctly-omitted instance-specific fields (provenance, schedule cursor, media binding) stay omitted.
Internal
- Bumps the exchange format to schema 1.1 (additive/minor) with a 1.0→1.1 migration that defaults the new fields, so older
1.0exports still import cleanly. Covered byschema.test.ts.
