Skip to content

Move moq.pub and moq.watch into the repo, keyed by path - #119

Merged
kixelated merged 4 commits into
mainfrom
claude/moq-pub-watch-integration-279e33
Aug 5, 2026
Merged

Move moq.pub and moq.watch into the repo, keyed by path#119
kixelated merged 4 commits into
mainfrom
claude/moq-pub-watch-integration-279e33

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

The two player sites lived in a separate repo (../moq.watch, which held both) and deployed by hand. This brings them in and makes them part of just deploy, and changes broadcasts to be named by the path instead of the query string.

There is no ../moq.pub — that repo held both pub/ and watch/. Its content is copied here; the repo itself is untouched on disk and can be archived.

URL scheme

/<project>/<name> — project is the relay tenant, name is everything after it (slashes allowed).

  • moq.pub/302/anon/lazy-otter-4f21.hang. Server-side, so the shareable URL exists before any JS runs, rather than being patched in by history.replaceState after load.
  • moq.watch/anon/lazy-otter-4f21.hang plays it back. The publisher page shows that link at the top, so sharing is one substitution.
  • moq.watch/ still shows a usage hint.
  • relay, jwt, source stay query params — they aren't part of a broadcast's identity.
  • Old ?project=&name= links 302 into the path, preserving the rest of the query.

Invented names are adjective-animal-hex.hang. I kept a random suffix because the original code deliberately used a UUID to avoid collisions in the open anon project, and two dictionary words alone is only ~500k combinations.

Layout

sites/
  lib/    broadcast.ts  route.ts  worker.ts  dev.ts  ← shared
  pub/    index.html  vite.config.ts  wrangler.jsonc  src/  public/
  watch/  …same

sites/lib holds the scheme once, used by both sites and by both the Worker and the Vite dev server, so production and just dev-pub can't drift on what a URL means.

Two things worth a reviewer's attention

assets.run_worker_first: ["/"] in each wrangler.jsonc is load-bearing. My first version returned 200 for moq.pub/ with no redirect — / matches index.html, so Cloudflare's asset server answers before the Worker ever runs. Easy to delete as noise later; it isn't.

The Worker can't use a dot to tell an asset from a page, since names end in .hang. The inherited pathname.includes(".") heuristic would have served /anon/x.hang as a static file. It now asks the asset store and falls back to the page on a 404.

Deploy

just build / just deploy do all three sites; just dev-pub (:5174) and just dev-watch (:5173) added. The player sites now read the repo-root .env.<mode> files, so PUBLIC_RELAY_URL is the single place the relay is set — the old copies hardcoded cdn.moq.dev, stale since the switch to cdn.moq.pro. Added PUBLIC_WATCH_URL for the cross-site link.

Staging is new.moq.pub / new.moq.watch, matching new.moq.dev. Wrangler will create those custom-domain DNS records on the first staging deploy — worth knowing before running it.

On the lockfile diff

163 lines, all esbuild, and it is not a version change. vite was only reachable through astro; declaring it directly (we import from it in three files and invoke its binary) re-hoists esbuild. vite requires ^0.25.0 and astro requires ^0.27.3, so they were always on separate copies:

hoisted esbuild nested
before 0.27.3 (astro's) vite/esbuild 0.25.12
after 0.25.12 (vite's) astro/esbuild 0.27.3

Each consumer resolves to the same version as before; only the tree position swapped.

Testing

  • just check and tsc clean. I probe-tested that biome actually covers sites/ rather than trusting the pass.
  • Full just build staging and just build live green, correct env baked into each bundle.
  • All four wrangler configs (2 sites × staging/live) pass --dry-run.
  • Routing exercised with curl against real wrangler dev for both Workers and against the Vite dev server: redirects, fresh name per request, nested names, percent-encoded names, hashed assets, favicons, legacy links, module/HMR requests. This caught a dev-only bug where /logo.svg was mistaken for a broadcast path and redirected — the dev plugin now only claims navigations, mirroring run_worker_first.
  • Both pages loaded in a browser. Publisher mounts on cdn.moq.pro/anon with the watch link; player connects and shows LIVE. No console errors.

One caveat: /demo/bbb.hang renders a black canvas. That's the demo not currently broadcasting, not this change — the live moq.dev/watch page shows the same black canvas for the same relay and name.

🤖 Generated with Claude Code

(written by Opus 5)

The two player sites lived in a separate repo and deployed by hand. Bring
them in as sites/pub and sites/watch, each still its own Worker but built
and uploaded by the same `just deploy`, and reading the repo-root .env
files so PUBLIC_RELAY_URL is the single place the relay is configured.
The old copies hardcoded cdn.moq.dev, stale since the move to cdn.moq.pro.

Broadcasts are now named by the path rather than the query string:
/<project>/<name>, e.g. moq.pub/anon/lazy-otter-4f21.hang. The same path
on moq.watch plays it back, so a publisher can share its own URL with one
substitution, and the publisher page links to it. A bare moq.pub/ redirects
to an invented name server-side, so the shareable URL exists before any JS
runs. Old ?project=&name= links redirect into the path. Everything that
isn't part of a broadcast's identity - relay, jwt, source - stays in the
query string.

sites/lib holds the scheme once, shared by both sites and by both the
Worker and the Vite dev server so the two can't drift.

Two things this had to work around:

- assets.run_worker_first is load-bearing. `/` matches index.html, so
  without it Cloudflare's asset server answers first and the Worker never
  runs, meaning no redirect and no invented name.
- The Worker can't tell an asset from a page by looking for a dot, since
  names end in .hang. It asks the asset store and falls back to the page
  on a 404.

Declaring vite as a direct devDependency (it was only reachable through
astro) re-hoists esbuild in the lockfile: vite wants ^0.25.0 and astro
wants ^0.27.3, so they were always on separate copies and each still
resolves to the same version as before. Only the tree position swapped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e19e61ffba

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread sites/pub/src/main.ts Outdated
The share link on moq.pub was built from the broadcast path alone, so a
broadcast published to a non-default relay produced a link that opened
moq.watch against the wrong relay and found nothing.

`relay` now carries over. `jwt` deliberately does not: moq tokens are
prefix-scoped with separate publish (`put`) and subscribe (`get`) grants,
so a token being used to publish carries `put`, and copying it into a link
meant to be passed around would hand every recipient the right to publish
rather than just to watch. Reviewers suggested carrying the whole query;
that trades a broken link for a credential leak.

Instead, a publisher who wants to share access to a private broadcast
passes a subscribe-only token as `?viewer=`, which becomes the `?jwt=` on
the moq.watch end. With a `jwt` and no `viewer` the page says viewers need
their own token, so the remaining gap is visible rather than looking like
a working link that silently won't connect.

Also deploy the player sites before moq.dev. Snapshot, moq.dev upload and
announce are effectively a transaction: a failure in the middle leaves
posts live but unannounced, and the snapshot expires after an hour, so a
later retry reads those posts as already-published and never mails them.
The two new deploys sat inside that window; now they run ahead of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@kixelated, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 43 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1fba066f-d1a8-4e42-bf70-1745d53fbec6

📥 Commits

Reviewing files that changed from the base of the PR and between e19e61f and 8f5957b.

📒 Files selected for processing (6)
  • CLAUDE.md
  • justfile
  • sites/env.d.ts
  • sites/lib/broadcast.ts
  • sites/pub/index.html
  • sites/pub/src/main.ts

Walkthrough

Added shared broadcast URL and relay utilities with route redirection and generated names. Added moq.pub and moq.watch sites with dedicated Vite and Cloudflare Worker configurations. Added publisher and playback entrypoints. Expanded build, deployment, and local development commands. Added environment configuration and updated repository documentation.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes moving moq.pub and moq.watch into the repository and switching to path-based broadcast URLs.
Description check ✅ Passed The description directly explains the repository migration, routing changes, deployment updates, configuration, and validation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/moq-pub-watch-integration-279e33

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CLAUDE.md`:
- Around line 72-79: Update the development-command description in CLAUDE.md to
identify the site-specific Vite commands—bun run dev:pub, bun run dev:watch,
just dev-pub, and just dev-watch—as the commands that use sites/lib/dev.ts, and
clarify that the root bun run dev starts Astro rather than moq.pub or moq.watch.

In `@justfile`:
- Around line 49-54: The _announce flow must skip subscriber notifications for
staging: gate the announcement credential/1Password setup behind env == live,
then exit or return before invoking notify-subscribers.ts send when env is
staging. Preserve the existing live announcement behavior and update the
_announce recipe rather than deploy.

In `@sites/lib/broadcast.ts`:
- Around line 20-23: Update parse() in broadcast.ts so malformed percent escapes
do not throw from decodeURIComponent while splitting pathname. Catch URIError
inside the path-parsing flow and return undefined for invalid broadcast paths,
preserving the existing successful parse behavior for valid inputs and letting
redirect() fall through on bad shared routes.

In `@sites/pub/src/main.ts`:
- Around line 28-36: Update the share-link construction in the main page flow to
preserve the selected relay as a query parameter in the watch URL, so
non-default relays remain usable by recipients. Keep the relay parameter
alongside the existing URL parameters and do not automatically include the JWT;
leave token distribution as an explicit policy decision.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b55a4a44-2c9f-45e8-a0bc-6ed860f8a871

📥 Commits

Reviewing files that changed from the base of the PR and between d311768 and e19e61f.

⛔ Files ignored due to path filters (9)
  • bun.lock is excluded by !**/*.lock
  • sites/pub/public/favicon.png is excluded by !**/*.png
  • sites/pub/public/favicon.svg is excluded by !**/*.svg
  • sites/pub/public/logo.svg is excluded by !**/*.svg
  • sites/pub/public/wordmark.svg is excluded by !**/*.svg
  • sites/watch/public/favicon.png is excluded by !**/*.png
  • sites/watch/public/favicon.svg is excluded by !**/*.svg
  • sites/watch/public/logo.svg is excluded by !**/*.svg
  • sites/watch/public/wordmark.svg is excluded by !**/*.svg
📒 Files selected for processing (23)
  • .env.development
  • .env.live
  • .env.staging
  • CLAUDE.md
  • README.md
  • justfile
  • package.json
  • sites/env.d.ts
  • sites/lib/broadcast.ts
  • sites/lib/dev.ts
  • sites/lib/name.ts
  • sites/lib/route.ts
  • sites/lib/worker.ts
  • sites/pub/index.html
  • sites/pub/src/main.ts
  • sites/pub/src/worker.ts
  • sites/pub/vite.config.ts
  • sites/pub/wrangler.jsonc
  • sites/watch/index.html
  • sites/watch/src/main.ts
  • sites/watch/src/worker.ts
  • sites/watch/vite.config.ts
  • sites/watch/wrangler.jsonc

Comment thread CLAUDE.md Outdated
Comment thread justfile Outdated
Comment thread sites/lib/broadcast.ts Outdated
Comment thread sites/pub/src/main.ts Outdated
kixelated and others added 2 commits August 5, 2026 09:15
`new URL` keeps a bad percent escape in the pathname, so a request to
moq.pub/anon/% reached decodeURIComponent in parse() and threw a URIError
straight out of the fetch handler. Verified against wrangler dev: 500
before, 200 after. It's now treated as "not a broadcast", which lands on
the invented-name redirect or the usage hint.

relay() had the same shape one function down: `?relay=` is whatever was in
the address bar and `new URL` throws on garbage, blanking the page. It now
falls back to the site's own relay. It also only accepts http and https --
`javascript:` and `data:` parse fine as URLs, mean nothing to
WebTransport, and are the shape that becomes an XSS sink as soon as
something renders the value.

Also correct CLAUDE.md: the dev plugin serves `just dev-pub` and
`just dev-watch`, not `bun run dev`, which is the Astro site.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The link was never asked for; I added it as a "nice touch" and it earned
its keep in review findings rather than use. To work it had to carry
?relay=, and the obvious next step of carrying the whole query would have
copied ?jwt= too, leaking a publish token to everyone the link reached.
The ?viewer= parameter existed only to escape that bind: a second,
subscribe-only token supplied purely so the link could be built.

None of that machinery is needed. The path symmetry is the actual feature
-- the same /<project>/<name> works on both sites -- and swapping the
hostname is something a person can do unaided.

Removes the link, the note, ?viewer=, Broadcast.watch(), PUBLIC_WATCH_URL
from all three .env files, and the layout reserve they needed. moq.watch's
usage hint still points at moq.pub; that's the empty state of a page with
nothing else to say, not a link hung over a working player.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kixelated
kixelated merged commit 3c0e0f6 into main Aug 5, 2026
1 check passed
@kixelated
kixelated deleted the claude/moq-pub-watch-integration-279e33 branch August 5, 2026 17:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant