Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 61 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,25 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

moq.dev is a web blog and demo for Media over QUIC (MoQ) protocol. It's built with Astro, Solid.js, and uses WebTransport to connect to MoQ relay servers for live streaming.

The repo holds three sites, each its own Cloudflare Worker but built and deployed together:

| Site | Source | What it is |
|-------------|-------------|-------------------------------------------------------|
| `moq.dev` | `src/` | The Astro blog and demos |
| `moq.pub` | `sites/pub` | A bare `<moq-publish-ui>` publisher, no framework |
| `moq.watch` | `sites/watch` | A bare `<moq-watch-ui>` player, no framework |

## Essential Commands

```bash
# Development
just dev # Start dev server with auto-open
just dev # Start the moq.dev dev server with auto-open
just dev-pub # moq.pub on :5174
just dev-watch # moq.watch on :5173

# Build & Deploy
just build # Production build
just deploy # Deploy to Cloudflare Pages (staging by default)
just build # Production build of all three sites
just deploy # Deploy all three to Cloudflare (staging by default)
just deploy live # Deploy to production, and email subscribers about new posts
just prod # Build and preview production locally

Expand Down Expand Up @@ -48,12 +58,59 @@ just fix # Auto-fix code formatting/lint issues
- **Authentication**: Basic JWT support via query parameters for demo broadcasts
- **Content Management**: MDX files in `src/pages/blog/` for documentation

### The moq.pub / moq.watch URL scheme

A broadcast is identified by its path: `/<project>/<name>`, e.g.
`moq.pub/anon/lazy-otter-4f21.hang`. The project is the relay tenant and the
name is everything after it, so names may contain slashes. The same path on
either site refers to the same broadcast — publish at `moq.pub/anon/x.hang` and
watch it back at `moq.watch/anon/x.hang`.

Anything that *isn't* part of the broadcast's identity stays in the query
string: `?relay=<url>`, `?jwt=<token>`, and (moq.pub only) `?source=camera`.

**moq.pub deliberately doesn't link to moq.watch.** The path symmetry is the
feature; a link on top of it isn't. It also can't be built honestly: a link has
to carry `?relay=` to reach the right relay, and the moment it carries `?jwt=`
too it leaks a publish token, because moq tokens are prefix-scoped with separate
publish (`put`) and subscribe (`get`) grants. Anyone sharing a broadcast can
swap the hostname themselves.

`sites/lib` holds the scheme itself, shared by both sites and by both the Worker
and the Vite dev server so they can't drift:

- `broadcast.ts` — parse and build `/<project>/<name>`, and the relay URL
- `route.ts` — which requests redirect: a bare `moq.pub/` invents a random name,
and old `?project=&name=` links move into the path
- `worker.ts` — the Worker both sites export
- `dev.ts` — a Vite plugin giving `just dev-pub` / `just dev-watch` the same
routing (`just dev` is the Astro site and doesn't use it)

Two things are easy to break here:

- `assets.run_worker_first: ["/"]` in each `wrangler.jsonc` is load-bearing. `/`
matches `index.html`, so without it Cloudflare's asset server answers first and
the Worker never runs — no redirect, no invented name.
- The Worker can't decide "asset vs. page" by looking for a dot, because names
end in `.hang`. It asks the asset store and falls back to the page on a 404.

### Deployment

- Cloudflare Pages via Wrangler
- Cloudflare Workers via Wrangler, one per site
- `just deploy` for staging, `just deploy live` for production
- Deploys are manual; nothing ships on merge to `main`

`just deploy <env>` builds all three sites in that mode and uploads each Worker.
The player sites read the repo-root `.env.<env>` files that the Astro site uses,
so `PUBLIC_RELAY_URL` is the single place the relay is configured. Staging is
`new.moq.dev`, `new.moq.pub`, and `new.moq.watch`.

The player sites deploy *before* moq.dev on purpose. Snapshot → moq.dev upload →
announce is effectively a transaction: a failure in the middle leaves posts live
but unannounced, and since the snapshot expires after an hour, a later retry
reads those posts as already-published and never mails them. Keep anything
fallible out from between those three steps.

**`just deploy live` mails the subscriber list.** `scripts/notify-subscribers.ts` snapshots the slugs in `https://moq.dev/rss.xml` before the upload, then sends a Resend broadcast for every post in the freshly built `dist/rss.xml` that wasn't in that snapshot. Subject and body come from the feed's `title` and `description`. A deploy that adds no posts sends nothing.

Credentials come from 1Password, so no secret has to sit on disk. `op.env` maps `RESEND_API_KEY` to `op://Corp/Resend/credential` and `op run` resolves it for the duration of the command. That file is committed on purpose: it holds references, not values. Install and sign in once with `brew install 1password-cli && op signin`.
Expand Down
29 changes: 23 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,46 @@
<img height="128px" src="https://github.com/moq-dev/moq.dev/blob/main/public/home/logo.svg" alt="Media over QUIC">
</p>

This repository contains the code for [moq.dev](https://moq.dev).
This repository contains the code for three sites:

This is a client only.
- [moq.dev](https://moq.dev) — the blog and demos, in `src/`
- [moq.pub](https://moq.pub) — a bare-bones publisher, in `sites/pub`
- [moq.watch](https://moq.watch) — a bare-bones player, in `sites/watch`

The player sites use the path to name a broadcast: publish at
`moq.pub/anon/lazy-otter-4f21.hang` and watch it back at
`moq.watch/anon/lazy-otter-4f21.hang`. Visiting [moq.pub](https://moq.pub) with
no path picks a random name for you.

These are clients only.
You'll either need to run a local server using [moq](https://github.com/moq-dev/moq) or use a public server such as `cdn.moq.pro`.

Join the [Discord](https://discord.moq.dev) for updates and discussion.

## Setup

Install the dependencies with `npm`:
Install the dependencies with `bun`:

```bash
npm i
bun i
```

## Development

Run the development web server:
Run a development web server:

```bash
npm run dev
just dev # moq.dev
just dev-pub # moq.pub, on :5174
just dev-watch # moq.watch, on :5173
```

## Deploy

`just deploy` builds and uploads all three sites to Cloudflare, staging by
default; `just deploy live` goes to production and mails subscribers about any
new blog posts.

## License

Licensed under either:
Expand Down
Loading
Loading