4.1 KiB
ECP-0123: Instant Station Guide and Player Warmup
Status: Draft
Problem statement
The hosted watch page can feel broken when /api/public-streams is slow, empty, or temporarily
unreachable: the channel rail waits on the network before showing stations. Even after stations
appear, the first channel tap pays the @moq/watch module import cost before the player element can
mount. That increases time to first playing frame and makes channel selection feel unreliable.
Constraints
- Keep the first screen a usable TV/player interface, not a marketing page.
- Preserve manual tuning, share links, DVR mode, public station refresh, and existing relay fields.
- Keep rollback simple: the live API remains authoritative after it answers.
- Avoid Chrome-only transport assumptions and keep live playback failure messages actionable.
- Keep the change static-site compatible so Cloudflare Worker asset deployment stays simple.
Decision
Embed the current starter LA station guide in index.html and render it immediately on first load.
Also keep a short-lived local guide cache, merge cached entries with the HTML seed, and refresh
/api/public-streams in the background with a bounded timeout. A slow or empty refresh no longer
clears already visible channels.
Change the web client, publisher CLI/module defaults, runbook examples, and remote watch E2E default
relay to https://relay.every.channel/anon. Default wt-publish and nbc-wt-publish to fMP4
passthrough so hotpatch-launched publishers emit the timescale and trackId CMAF catalog metadata
expected by the hosted @moq/watch element. Preload/preconnect the primary player dependencies and
warm the @moq/watch custom element after first paint. Let @moq/watch use its available live
transport fallback instead of forcing WebTransport-only playback. Add client performance marks for
guide first render, guide fetch, watch request, player module readiness, player mount, catalog/live
status, and first observable canvas frame when the browser exposes it.
Alternatives considered
- Wait only for
/api/public-streams. Rejected because the UI becomes blank when the directory is slow and because children should be able to tap visible channels immediately. - Make the API response cacheable at the CDN. Rejected as the only fix because active streams are short TTL and stale-but-visible fallback belongs in the client.
- Bundle
@moq/watchinto the static site. Deferred because CDN import fallback is already in use; warming and modulepreload reduce tap latency without changing the build graph.
Rollout / teardown plan
Ship the static web change with the existing Worker asset deploy and roll the publisher hotpatch
binary to the LA nodes so their catalogs match the current watcher schema. Validate with clean-cache
desktop/mobile browser loads and check the app's window.__ecPerf marks plus a live tune through the
public relay. The Forgejo workflows use the locally registered namespace-profile-linux-medium
runner label and ecp-forge runs a persistent forgejo-runner-agent service with a normal shell
tool PATH so the Cloudflare deploy can actually leave the queue on the self-hosted forge. Teardown is
removing the HTML seed/cache/warmup path, returning to live-API-only station rendering, and
explicitly passing --passthrough=false only if an older watcher path is restored.
Forgejo CI and deploy jobs run inside the repository Nix dev shell instead of downloading generic Linux Rust, Trunk, age, or Node binaries. This keeps self-hosted NixOS runners reproducible and prevents dynamic-linker failures from blocking the Cloudflare asset rollout.
The workflow ECP gate starts at ECP-0120 because older proposals predate the current lint shape.
The lint script uses ripgrep when available and falls back to GNU grep on the Forgejo runner.
Cloudflare deployment decrypts the API token through the repo's agenix workflow and builds web
assets through scripts/build-web.sh so local and runner build paths stay aligned.
All Rust gates in Forgejo, including duplicate-publisher proof and simulation checks, enter through
the same Nix dev shell so later CI additions do not silently depend on rustup state.