Skip to content

Conversation

@kixelated
Copy link
Collaborator

@kixelated kixelated commented Aug 25, 2025

Summary by CodeRabbit

  • New Features

    • Cloudflare relay integration via a new public URL for publishing and watching.
    • Added “Hosted on Cloudflare” indicators and Broadcast headers.
    • Enabled HTTPS endpoint and improved load balancing reliability.
  • UI/Style

    • Revamped global layout with centered, shadowed container.
    • Added feature lists to Publish/Watch and a watch-page notice.
  • Documentation

    • New blog posts: “The First MoQ App: hang.live” and “Cloudflare’s MoQ CDN.”
    • Updated homepage mentions, links, and multiple docs sections (Issues, Source).
  • Chores

    • Environment-based deploy configuration and routes.
    • Updated infrastructure images and ports.

@kixelated kixelated enabled auto-merge (squash) August 25, 2025 18:34
@coderabbitai
Copy link

coderabbitai bot commented Sep 4, 2025

Walkthrough

Adds a new public env var PUBLIC_CLOUDFLARE_URL and uses it in publish/watch components. Updates Vite optimizeDeps to exclude @kixelated/hang and switches the dependency to a local link. Adjusts deployment (wrangler envs, just deploy param), adds preview, and revises infra (image tags, firewall TCP 443, health check relocation, relay config). Adds layout tweaks and two blog posts.

Changes

Cohort / File(s) Summary
Env vars and typing
./.env.development, ./.env.production, env.d.ts
Adds PUBLIC_CLOUDFLARE_URL in dev/prod env files and types it in ImportMetaEnv.
Frontend components
src/components/publish.tsx, src/components/watch.tsx, src/pages/watch.astro, src/layouts/global.astro
Components now use PUBLIC_CLOUDFLARE_URL (non-bbb in watch). UI content/structure updates: new “Broadcast/Features/Hosted” blocks, conditional Cloudflare section, watch warning paragraph, and reworked global layout styling/navigation.
Build/tooling and deployment
astro.config.ts, package.json, justfile, wrangler.jsonc
Vite optimizeDeps excludes "@kixelated/hang". Dependency switched to local link: ../moq/js/hang. just deploy accepts env param; adds preview target. Wrangler moves to env-based config with staging/live and renames local name.
Infrastructure (Terraform/templates)
infra/input.tf, infra/relay.tf, infra/relay-lb.tf, infra/relay.yml.tpl
Bumps Docker image tags (relay 0.9.2, hang 0.2.9); removes Cloudflare tokens. Adds HTTP health check in relay-lb and references it; removes duplicate from relay.tf. Firewall allows TCP 443. relay.yml.tpl consolidates TLS arrays, adds [web.https], and adds iptables TCP:443 allow in service.
Content updates and new posts
src/pages/blog/first-app.mdx, src/pages/blog/first-cdn.mdx, src/pages/index.mdx, src/pages/issues.mdx, src/pages/source.mdx
Adds two blog posts (first app, first CDN). Updates homepage text/links, issues list and tables, and source page repo link/description.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Poem

I nibbled the configs with delicate care,
New clouds in my burrow, URLs in the air.
Ports now welcome TCP’s gentle knock,
Health checks hop steady around the clock.
Posts sprout fresh like clover in spring—
Deploy with a twitch, preview on wing.
Thump-thump: ship it, and sing! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cloudflare

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@kixelated kixelated disabled auto-merge September 4, 2025 23:21
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
infra/relay.yml.tpl (1)

106-119: Make iptables rules idempotent to avoid duplicates on restarts.

Repeated ExecStart will append duplicates. Check before adding.

 [Service]
 Type=oneshot
 RemainAfterExit=true
-ExecStart=iptables -A INPUT -p udp --dport 443 -j ACCEPT
-ExecStart=iptables -A INPUT -p tcp --dport 443 -j ACCEPT
+ExecStart=/bin/sh -c 'iptables -C INPUT -p udp --dport 443 -j ACCEPT || iptables -A INPUT -p udp --dport 443 -j ACCEPT'
+ExecStart=/bin/sh -c 'iptables -C INPUT -p tcp --dport 443 -j ACCEPT || iptables -A INPUT -p tcp --dport 443 -j ACCEPT'
🧹 Nitpick comments (29)
.env.production (1)

4-4: New PUBLIC_CLOUDFLARE_URL looks fine; consider lint-friendly formatting and secret hygiene.

  • Optional: drop quotes to satisfy dotenv-linter and keep keys alphabetized with the other PUBLIC_* vars.
  • Verify no real tokens are committed in .env.production; prefer Cloudflare Pages/Wrangler secrets for production.

Apply if you want the lint fix:

-PUBLIC_CLOUDFLARE_URL="https://relay.cloudflare.mediaoverquic.com"
+PUBLIC_CLOUDFLARE_URL=https://relay.cloudflare.mediaoverquic.com
src/pages/source.mdx (1)

23-23: Minor wording tweak for flow.

Suggest a tiny grammar polish.

-|                    [hang-gst](https://github.com/kixelated/hang-gst) | A GStreamer plugin for publishing and consuming media. Separate repository to avoid GStreamer build dependency. |
+|                    [hang-gst](https://github.com/kixelated/hang-gst) | A GStreamer plugin for publishing and consuming media. Separate repository to avoid a GStreamer build dependency. |
infra/relay.tf (1)

92-109: TCP 443 opened — consider enabling flow logs and future origin hardening

Nice addition. Two follow-ups:

  • Enable firewall logging to aid debugging/forensics.
  • If you later front this with Cloudflare only, consider restricting source_ranges to Cloudflare IPs to block direct origin hits.

Apply to enable logging:

 resource "google_compute_firewall" "relay" {
   name    = "relay"
   network = "default"

   allow {
     protocol = "udp"
     ports    = ["443"]
   }

   allow {
     protocol = "tcp"
     ports    = ["443"]
   }

+  log_config {
+    metadata = "INCLUDE_ALL_METADATA"
+  }
+
   source_ranges = ["0.0.0.0/0"]
   target_tags   = ["relay"]
 }
justfile (1)

66-67: Add a Cloudflare-runtime preview

Astro preview runs on Node, not the CF runtime. Consider a CF preview for parity.

Add alongside the current target:

preview-cf: build
	pnpm wrangler pages dev ./dist
src/pages/issues.mdx (1)

20-20: Asterisk on Firefox — clarify support details

Add a short note (version/flags/nightly) or remove the asterisk to avoid ambiguity.

infra/relay-lb.tf (1)

75-81: Health check is commented out

This resource sits inside a block comment and won’t be created. If the intent was to move it from infra/relay.tf into active use, relocate it outside the comment (and ensure any backend service references are also active). Otherwise consider removing to reduce confusion.

Example (uncommented):

resource "google_compute_http_health_check" "relay" {
  name               = "relay"
  request_path       = "/health"
  check_interval_sec = 5
  timeout_sec        = 5
}
infra/input.tf (1)

1-24: Add explicit types and light validation on inputs.

Improves Terraform plan safety and earlier failure on misconfig.

Example:

 variable "project" {
+  type        = string
   description = "project id"
   default     = "quic-video"
 }
 
 variable "region" {
+  type        = string
   description = "region"
   default     = "us-central1"
 }
 
 variable "zone" {
+  type        = string
   description = "zone"
   default     = "us-central1-a"
 }
 
 variable "email" {
+  type        = string
   description = "Your email address, used for LetsEncrypt"
   default     = "admin@moq.dev"
+  validation {
+    condition     = can(regex("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$", var.email))
+    error_message = "email must be a valid email address."
+  }
 }
 
 variable "domain" {
+  type        = string
   description = "domain name"
   default     = "moq.dev"
 }
wrangler.jsonc (2)

5-7: Ensure local deploy maps to the dev environment
Add a local deploy recipe in justfile (for example, dev-deploy) that invokes

pnpm wrangler deploy --env dev

so it uses the moq-dev-local name and avoids accidentally pushing the wrong target.


15-31: Use routes array instead of route and validate config with --dry-run

  • Replace the singular "route" block with a "routes" array to support multiple patterns, e.g.:
    -      "route": {
    -        "pattern": "new.moq.dev",
    -        "custom_domain": true
    -      }
    +      "routes": [
    +        { "pattern": "new.moq.dev", "custom_domain": true }
    +      ]
    (developers.cloudflare.com)
  • Run npx wrangler deploy --dry-run to compile and catch config/schema errors before actual deployment
    (developers.cloudflare.com)
src/layouts/global.astro (3)

32-35: Image perf: lazy-load non-critical nav icons and set explicit dimensions to reduce CLS.

Logo should stay eager; other icons can be lazy and sized.

-<img src="/layout/watch.svg" class="w-32" alt="Watch" />
+<img src="/layout/watch.svg" class="w-32" width="128" height="128" loading="lazy" alt="Watch" />
-<img src="/layout/publish.svg" class="w-32" alt="Publish" />
+<img src="/layout/publish.svg" class="w-32" width="128" height="128" loading="lazy" alt="Publish" />
-<img src="/layout/explain.svg" class="w-32" alt="Blog" />
+<img src="/layout/explain.svg" class="w-32" width="128" height="128" loading="lazy" alt="Blog" />
-<img src="/layout/source.svg" class="w-32" alt="Source" />
+<img src="/layout/source.svg" class="w-32" width="128" height="128" loading="lazy" alt="Source" />
-<img src="/layout/discord.svg" class="w-32" alt="Discord" />
+<img src="/layout/discord.svg" class="w-32" width="128" height="128" loading="lazy" alt="Discord" />

Also applies to: 41-56


53-55: External link hardening for Discord.

Open in a new tab and add rel to prevent tab‑napping.

-<a href="https://discord.gg/FCYF3p99mr">
+<a href="https://discord.gg/FCYF3p99mr" target="_blank" rel="noopener noreferrer">

61-63: Date rendering can shift by timezone; format deterministically.

Use UTC or an explicit format to avoid off‑by‑one rendering across locales.

- published {new Date(frontmatter.date).toLocaleDateString()}
+ published {new Date(frontmatter.date + 'T00:00:00Z').toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: '2-digit', timeZone: 'UTC' })}
src/pages/blog/first-app.mdx (1)

32-35: Conciseness nit.

Minor copy tweak.

-And of course, all of the functionality (except for user accounts) uses Media over QUIC.
+And of course, all the functionality (except for user accounts) uses Media over QUIC.
infra/relay.yml.tpl (2)

87-105: Harden the systemd unit and auto-start on boot.

Add basic sandboxing and ensure services survive reboots.

 [Service]
 Restart=on-failure
 RestartSec=5s
+NoNewPrivileges=true
+ProtectSystem=full
+ProtectHome=true
+PrivateTmp=true
 ExecStart=docker run --rm \
@@
 ExecStop=docker stop moq-relay

And in runcmd:

 runcmd:
   - systemctl daemon-reload
   - systemctl restart docker
+  - systemctl enable allow-quic
+  - systemctl enable moq-relay
   - systemctl start moq-relay

33-33: Typo in comment.

“Unfortuantely” → “Unfortunately”.

-  # Unfortuantely, cos-cloud doesn't seem to support the ca_certs module
+  # Unfortunately, cos-cloud doesn't seem to support the ca_certs module
src/pages/blog/first-cdn.mdx (4)

43-48: Consider referencing the configured env var instead of hardcoding the Cloudflare URL in examples.

To keep docs and UI consistent and avoid drift, prefer showing examples that read from PUBLIC_CLOUDFLARE_URL (or mention it explicitly in the prose) rather than hardcoding https://relay.cloudflare.mediaoverquic.com.

Example snippet for HTML components:

<!-- Example showing how to inject the URL from a build-time env -->
<hang-publish url="%PUBLIC_CLOUDFLARE_URL%" name="unique-name-abc123" audio video controls captions></hang-publish>
<hang-watch url="%PUBLIC_CLOUDFLARE_URL%" name="unique-name-abc123" muted controls captions></hang-watch>

Also applies to: 57-62, 173-206, 212-226


84-88: Highlight the risk of guessable names more explicitly.

Since there’s no auth, call out that broadcasts are public-by-name and recommend sufficiently random names (e.g., 128-bit tokens) to prevent hijacking or unwanted watchers.


20-23: Add lazy-loading for images to improve LCP.

Images in figures can be loading="lazy" and decoding="async" to reduce page weight.

Apply this diff:

-	![Cloudflare logo](/blog/first-cdn/cloudflare.png)
+	![Cloudflare logo](/blog/first-cdn/cloudflare.png){loading="lazy" decoding="async"}
-	![A global CDN](/blog/first-cdn/global.png)
+	![A global CDN](/blog/first-cdn/global.png){loading="lazy" decoding="async"}

Also applies to: 97-100


134-137: Defer the YouTube iframe for better performance.

Add loading="lazy" to the iframe.

-	<iframe width="560" height="315" src="https://www.youtube.com/embed/ZXsQAXx_ao0?si=xsAscm04CnwAer4b" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen style="margin: 0 auto;"></iframe>
+	<iframe loading="lazy" width="560" height="315" src="https://www.youtube.com/embed/ZXsQAXx_ao0?si=xsAscm04CnwAer4b" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen style="margin: 0 auto;"></iframe>
src/pages/watch.astro (1)

10-12: Tighten the copy.

Suggest replacing “Pls” with “Please” and optionally link a reporting channel.

-		Watching a <strong>PUBLIC</strong> broadcast. Pls report any bad stuff.
+		Watching a <strong>PUBLIC</strong> broadcast. Please report any abuse via <a class="underline" href="/issues">Issues</a>.
.env.development (1)

1-3: Silence dotenv-linter warnings (quotes + ordering).

Remove quotes and place PUBLIC_CLOUDFLARE_URL before PUBLIC_RELAY_TOKEN to satisfy linters. No functional change.

-PUBLIC_RELAY_URL="http://localhost:4443"
-PUBLIC_RELAY_TOKEN=""
-PUBLIC_CLOUDFLARE_URL="https://relay.cloudflare.mediaoverquic.com"
+PUBLIC_RELAY_URL=http://localhost:4443
+PUBLIC_CLOUDFLARE_URL=https://relay.cloudflare.mediaoverquic.com
+PUBLIC_RELAY_TOKEN=
src/components/watch.tsx (4)

10-15: Guard against missing env and invalid URL.

If PUBLIC_CLOUDFLARE_URL is empty at build/run time, new URL() throws. Add a fallback or a clearer error.

-	let url: URL;
+	let url: URL;
 	if (name === "bbb") {
 		url = new URL(`${import.meta.env.PUBLIC_RELAY_URL}/demo?jwt=${import.meta.env.PUBLIC_RELAY_TOKEN}`);
 	} else {
-		url = new URL(import.meta.env.PUBLIC_CLOUDFLARE_URL);
+		const cf = import.meta.env.PUBLIC_CLOUDFLARE_URL;
+		if (!cf) {
+			console.warn("PUBLIC_CLOUDFLARE_URL is not set; falling back to PUBLIC_RELAY_URL.");
+			url = new URL(import.meta.env.PUBLIC_RELAY_URL);
+		} else {
+			url = new URL(cf);
+		}
 	}

58-63: Harden external link attributes.

For target="_blank", include noopener to prevent window.opener access.

-				<a href="/blog/first-cdn" rel="noreferrer" target="_blank">
+				<a href="/blog/first-cdn" rel="noopener noreferrer" target="_blank">

30-56: Avoid duplication of the “Features” list across pages.

The same list exists in publish.tsx. Extract a small shared component (e.g., Features.tsx) to DRY this up.


11-15: Minimize magic values.

"bbb" is a hidden feature flag. Consider centralizing the demo name in a constant and documenting the behavior.

-	if (name === "bbb") {
+	const DEMO_NAME = "bbb";
+	if (name === DEMO_NAME) {
src/components/publish.tsx (4)

8-9: Guard against missing env and invalid URL.

Add a check before constructing the URL to avoid runtime throws when the env var is missing.

-	const url = new URL(import.meta.env.PUBLIC_CLOUDFLARE_URL);
+	const cf = import.meta.env.PUBLIC_CLOUDFLARE_URL;
+	const url = new URL(cf || import.meta.env.PUBLIC_RELAY_URL);

16-19: Harden external link attributes.

Add noopener when using target="_blank".

-				<a href={`/watch?name=${name}`} rel="noreferrer" target="_blank" class="ml-2 text-2xl">
+				<a href={`/watch?name=${name}`} rel="noopener noreferrer" target="_blank" class="ml-2 text-2xl">

36-67: DRY the “Features” list.

Same recommendation as in watch.tsx: pull into a shared component.


65-67: Defer non-critical image loading.

Add lazy-loading and async decoding to the Cloudflare logo.

-				<img src="/blog/first-cdn/cloudflare.png" alt="Cloudflare" class="w-64" />
+				<img src="/blog/first-cdn/cloudflare.png" alt="Cloudflare" class="w-64" loading="lazy" decoding="async" />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b4da7dd and 03cdb5c.

⛔ Files ignored due to path filters (8)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • public/blog/first-app/demo.png is excluded by !**/*.png
  • public/blog/first-app/icon.png is excluded by !**/*.png
  • public/blog/first-app/preview.png is excluded by !**/*.png
  • public/blog/first-app/snarky.png is excluded by !**/*.png
  • public/blog/first-cdn/cloudflare.png is excluded by !**/*.png
  • public/blog/first-cdn/global.png is excluded by !**/*.png
  • public/layout/favicon.svg is excluded by !**/*.svg
📒 Files selected for processing (20)
  • .env.development (1 hunks)
  • .env.production (1 hunks)
  • astro.config.ts (1 hunks)
  • env.d.ts (1 hunks)
  • infra/input.tf (1 hunks)
  • infra/relay-lb.tf (1 hunks)
  • infra/relay.tf (2 hunks)
  • infra/relay.yml.tpl (2 hunks)
  • justfile (1 hunks)
  • package.json (1 hunks)
  • src/components/publish.tsx (2 hunks)
  • src/components/watch.tsx (2 hunks)
  • src/layouts/global.astro (2 hunks)
  • src/pages/blog/first-app.mdx (1 hunks)
  • src/pages/blog/first-cdn.mdx (1 hunks)
  • src/pages/index.mdx (3 hunks)
  • src/pages/issues.mdx (2 hunks)
  • src/pages/source.mdx (1 hunks)
  • src/pages/watch.astro (1 hunks)
  • wrangler.jsonc (2 hunks)
🧰 Additional context used
🪛 dotenv-linter (3.3.0)
.env.development

[warning] 3-3: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)


[warning] 3-3: [UnorderedKey] The PUBLIC_CLOUDFLARE_URL key should go before the PUBLIC_RELAY_TOKEN key

(UnorderedKey)

.env.production

[warning] 4-4: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)


[warning] 4-4: [UnorderedKey] The PUBLIC_CLOUDFLARE_URL key should go before the PUBLIC_RELAY_TOKEN key

(UnorderedKey)

🪛 GitHub Check: check
src/components/publish.tsx

[failure] 12-12:
Property 'hang-support' does not exist on type 'JSX.IntrinsicElements'.

src/components/watch.tsx

[failure] 28-28:
Property 'hang-watch' does not exist on type 'JSX.IntrinsicElements'.


[failure] 26-26:
Property 'hang-watch' does not exist on type 'JSX.IntrinsicElements'.


[failure] 19-19:
Property 'hang-support' does not exist on type 'JSX.IntrinsicElements'.

🪛 GitHub Actions: pr
src/components/publish.tsx

[error] 12-12: TypeScript error TS2339: Property 'hang-support' does not exist on type 'JSX.IntrinsicElements'.

🪛 LanguageTool
src/pages/blog/first-cdn.mdx

[grammar] ~11-~11: There might be a mistake here.
Context: ...Cloudflare 🚨 It's finally happening! 🚨 Cloudflare has [just announced](https://...

(QB_NEW_EN)


[grammar] ~14-~14: There might be a mistake here.
Context: ...MoQ on their massive, anycast network. Try it out, and convince your boss' boss...

(QB_NEW_EN)


[grammar] ~34-~34: There might be a mistake here.
Context: .../main/js/hang) (smash that star button). You can publish a live broadcast in the ...

(QB_NEW_EN)


[grammar] ~65-~65: There might be a mistake here.
Context: ...PI) and transmitted using MoQ of course. But that's a whole separate blog post; i...

(QB_NEW_EN)


[grammar] ~69-~69: There might be a mistake here.
Context: ...e get access to individual video frames. There's a super secret section at the ...

(QB_NEW_EN)


[grammar] ~73-~73: There might be a mistake here.
Context: ...t side but WebSupport is a big deal. We are no longer forced to use WebRTC, b...

(QB_NEW_EN)


[style] ~86-~86: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...sts start/stop, so that won't work. - **There's no [Safari support](https://caniuse.c...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~89-~89: ‘in the meantime’ might be wordy. Consider a shorter alternative.
Context: ...m/kixelated/moq/tree/main/rs/moq-relay) in the meantime. I've been adding new features and fixi...

(EN_WORDINESS_PREMIUM_IN_THE_MEANTIME)


[style] ~93-~93: Consider setting off ‘too’ with commas if you want to emphasize an abrupt change of thought.
Context: .../relay.tf) that powers relay.moq.dev. You too can run your own "global" CDN with 3 no...

(MISSING_COMMA_WITH_TOO)


[grammar] ~108-~108: There might be a mistake here.
Context: ...thy is a tragedy and boredom is a crime. > - [Bo Burnham](https://www.youtube.co...

(QB_NEW_EN)


[grammar] ~116-~116: There might be a mistake here.
Context: ...efore there's been any real world usage. It's been over 3 years since I fought Am...

(QB_NEW_EN)


[style] ~117-~117: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...archive/id/draft-lcurley-warp-00.html). It's going to be at least another 3 years ...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[grammar] ~122-~122: There might be a mistake here.
Context: ... in 2015, with the RFC released in 2021. And they had a boatload of production da...

(QB_NEW_EN)


[grammar] ~129-~129: There might be a mistake here.
Context: ... the mentality that MoQ needs right now. Just build something. **Just release...

(QB_NEW_EN)


[grammar] ~130-~130: There might be a mistake here.
Context: ...eds right now. Just build something. Just release something. **Just do it...

(QB_NEW_EN)


[grammar] ~131-~131: There might be a mistake here.
Context: ...something**. Just release something. Just do it.

<iframe width...

(QB_NEW_EN)


[grammar] ~149-~149: There might be a mistake here.
Context: ...LS / RTMP using relatively new Web APIs. Don't judge MoQ based on these initial o...

(QB_NEW_EN)


[grammar] ~150-~150: There might be a mistake here.
Context: ...ge MoQ based on these initial offerings. We've got a ton of work to do. **Let...

(QB_NEW_EN)


[grammar] ~155-~155: There might be a mistake here.
Context: ...). Somehow there's 900+ people in there. Ping me and I will do whatever I can to ...

(QB_NEW_EN)


[grammar] ~156-~156: There might be a mistake here.
Context: ...me and I will do whatever I can to help. Especially if it means putting one mor...

(QB_NEW_EN)


[grammar] ~170-~170: There might be a mistake here.
Context: ...nge and is probably already out of date. When in doubt, [consult the source code]...

(QB_NEW_EN)


[grammar] ~208-~208: There might be a mistake here.
Context: ...cret* features behind undocumented APIs. Like running an object detection model i...

(QB_NEW_EN)


[grammar] ~209-~209: There might be a mistake here.
Context: ... Like running an object detection model in browser and publishing the results as a...

(QB_NEW_EN)


[grammar] ~209-~209: There might be a mistake here.
Context: ...d publishing the results as a MoQ track. Stay tuned for a blog post about that if...

(QB_NEW_EN)


[grammar] ~210-~210: There might be a mistake here.
Context: ...out a better use-case than a cat cam. 🐈 typescript // Publish a broadcast. const publish = new Watch.Publish({ enabled: true, url: "https://relay.cloudflare.mediaoverquic.com", name: "unique-name-abc123", device: "camera", video: { enabled: true, detection: { enabled: true, } }, }) Also, for the record, Typescript is real...

(QB_NEW_EN)


[grammar] ~228-~228: There might be a mistake here.
Context: ...r the record, Typescript is really nice. 🤮 Javascript 🤮 is still an abomination...

(QB_NEW_EN)

src/pages/blog/first-app.mdx

[grammar] ~11-~11: There might be a mistake here.
Context: ... hang.live 🚨 It's finally happening! 🚨 4 months ago I...

(QB_NEW_EN)


[style] ~13-~13: The word ‘quit’ might seem too casual. In this context, consider opting for a more formal alternative.
Context: ... months ago](/blog/application-first) I quit my job and went all-in on MoQ. 4 months...

(QUIT_MY_JOB)


[grammar] ~22-~22: There might be a mistake here.
Context: ...igure> hang.live is place to hang out with friends after a ...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ... hang out with friends after a long day. It's meant to be fun and stupid. Everyth...

(QB_NEW_EN)


[style] ~32-~32: Consider removing “of” to be more concise
Context: ... And of course, all of the functionality (except for user accounts...

(ALL_OF_THE)


[grammar] ~39-~39: There might be a mistake here.
Context: ...vering around my office. ## Open Source Even if you don't want to use a site des...

(QB_NEW_EN)


[grammar] ~44-~44: There might be a mistake here.
Context: ... the x,y,z coordinates of a broadcast. However, every component is opt-in via...

(QB_NEW_EN)


[grammar] ~47-~47: There might be a mistake here.
Context: ...e contractors that shall not be named. I want to help you grow that list, [jo...

(QB_NEW_EN)


[grammar] ~48-~48: There might be a mistake here.
Context: ...Discord](https://discord.gg/FCYF3p99mr). Seriously, this has become a personal ve...

(QB_NEW_EN)


[grammar] ~51-~51: There might be a mistake here.
Context: ... vibe coded UI is closed source for now. I still have ambitions of [hang.live](ht...

(QB_NEW_EN)


[grammar] ~72-~72: There might be a mistake here.
Context: ...t of meme videos and sounds; try /brb. Seriously just [try it out](https://hang...

(QB_NEW_EN)


[grammar] ~78-~78: There might be a mistake here.
Context: .... ## Huge Disclaimer This shit is hard. Nothing has been optimized and the user ...

(QB_NEW_EN)


[grammar] ~79-~79: There might be a mistake here.
Context: ...erience still leaves much to be desired. Just like the [Cloudflare CDN](/blog/fir...

(QB_NEW_EN)


[style] ~83-~83: As an alternative to the over-used intensifier ‘really’, consider replacing this phrase.
Context: ...olled by Google for over a decade. It's really good at powering Google Meet, but it gets ex...

(EN_WEAK_ADJECTIVE)


[style] ~96-~96: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...erated and just needs to be toned down. Maybe we don't need hundreds of wiggling line...

(REP_MAYBE)

🔇 Additional comments (12)
env.d.ts (1)

6-6: Typing for PUBLIC_CLOUDFLARE_URL: LGTM.

Correctly scoped as a public string on ImportMetaEnv.

astro.config.ts (1)

39-41: Excluding @kixelated/hang from optimizeDeps: LGTM.

Appropriate when using a linked package; harmless otherwise since optimizeDeps is dev-only.

src/pages/index.mdx (3)

11-11: Cloudflare mention — LGTM

Reads well and matches the rest of the PR’s Cloudflare positioning.


42-42: No action needed

Trailing newline/formatting looks fine.


27-28: Verified gstreamer and ffmpeg URLs — both repositories exist and match the intended targets.

justfile (1)

57-59: Clarify deployment target as Cloudflare Workers
justfile lines 57–59

-# Deploy the site to Cloudflare Pages
+# Deploy to Cloudflare Workers
 deploy env="staging": build
   pnpm wrangler deploy --env {{env}}

Likely an incorrect or invalid review comment.

src/pages/issues.mdx (2)

24-27: New “What works” bullets — LGTM

Clear and helpful additions.


58-58: No action needed

Formatting tweak is fine.

infra/input.tf (1)

1-68: No remaining Cloudflare token references
Confirmed no occurrences of cloudflare_dns_token or cloudflare_zone_token in the codebase.

infra/relay.yml.tpl (1)

59-69: TLS/web config looks consistent; manual verification needed for multi-cert arrays and listeners
The grep yielded no output—please confirm the deployed moq-relay version supports multi-cert arrays and the [web.https] block, and verify (e.g. via journalctl or your logs) that both QUIC (UDP 443) and HTTPS (TCP 443) listeners are bound and certificates loaded.

.env.development (1)

3-3: Ensure production parity.

Verify .env.production also defines PUBLIC_CLOUDFLARE_URL with the intended value (or a Cloudflare Preview value).

Would you like a quick script to diff environment keys across environments?

src/components/watch.tsx (1)

19-28: Declare hang- custom elements in JSX*
Add a global JSX augmentation (e.g. src/types/hang-elements.d.ts) with IntrinsicElements entries for "hang-support", "hang-publish", and "hang-watch" (typed as any) to unblock the TSX errors.

Comment on lines 26 to 34
variable "docker_relay" {
description = "Docker image for moq-relay"
default = "docker.io/kixelated/moq-relay:0.8.8"
default = "docker.io/kixelated/moq-relay:0.9.2"
}

variable "docker_hang" {
description = "Docker image for hang"
default = "docker.io/kixelated/hang:0.2.5"
}

variable "cloudflare_dns_token" {
description = "Cloudflare DNS API token - edit permissions for moq.dev"
sensitive = true
}

variable "cloudflare_zone_token" {
description = "Cloudflare Zone API token - read permissions for all zones"
sensitive = true
default = "docker.io/kixelated/hang:0.2.9"
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Pin Docker images by digest to prevent supply‑chain drift.

Tags are mutable; use immutable digests for deterministic, auditable deploys.

Apply:

-variable "docker_relay" {
-  description = "Docker image for moq-relay"
-  default     = "docker.io/kixelated/moq-relay:0.9.2"
-}
+variable "docker_relay" {
+  description = "Docker image for moq-relay"
+  # Example with digest; update to the correct digest from your registry
+  default     = "docker.io/kixelated/moq-relay@sha256:<relay-digest>"
+}
 
-variable "docker_hang" {
-  description = "Docker image for hang"
-  default     = "docker.io/kixelated/hang:0.2.9"
-}
+variable "docker_hang" {
+  description = "Docker image for hang"
+  # Example with digest; update to the correct digest from your registry
+  default     = "docker.io/kixelated/hang@sha256:<hang-digest>"
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
variable "docker_relay" {
description = "Docker image for moq-relay"
default = "docker.io/kixelated/moq-relay:0.8.8"
default = "docker.io/kixelated/moq-relay:0.9.2"
}
variable "docker_hang" {
description = "Docker image for hang"
default = "docker.io/kixelated/hang:0.2.5"
}
variable "cloudflare_dns_token" {
description = "Cloudflare DNS API token - edit permissions for moq.dev"
sensitive = true
}
variable "cloudflare_zone_token" {
description = "Cloudflare Zone API token - read permissions for all zones"
sensitive = true
default = "docker.io/kixelated/hang:0.2.9"
}
variable "docker_relay" {
description = "Docker image for moq-relay"
# Example with digest; update to the correct digest from your registry
default = "docker.io/kixelated/moq-relay@sha256:<relay-digest>"
}
variable "docker_hang" {
description = "Docker image for hang"
# Example with digest; update to the correct digest from your registry
default = "docker.io/kixelated/hang@sha256:<hang-digest>"
}
🤖 Prompt for AI Agents
In infra/input.tf around lines 26 to 34, the variables define Docker images
using mutable tags which can drift; update them to pinned image digests
(image@sha256:<digest>) instead of tag-only defaults. Obtain the exact sha256
digests from the image registry (docker pull or registry API) for
docker.io/kixelated/moq-relay:0.9.2 and docker.io/kixelated/hang:0.2.9 and
replace the default values with the corresponding image@sha256:<digest> strings,
or alternatively make the variables required (no default) and document that
callers must supply a digest-pinned image; ensure any other references in the
code use the new digest variables.

},
"dependencies": {
"@kixelated/hang": "^0.3.9",
"@kixelated/hang": "link:../moq/js/hang",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Local link dependency will break CI/Cloudflare builds.

link:../moq/js/hang assumes a sibling repo on disk and won’t exist in remote builders, causing installs/builds to fail. Use a published semver or a Git dependency for reproducible builds; keep local linking as a dev-only override.

Recommended revert for main:

-    "@kixelated/hang": "link:../moq/js/hang",
+    "@kixelated/hang": "^0.3.9",

Dev-only options:

  • Use pnpm link --global locally without committing.
  • Or add a per-developer .npmrc with @kixelated:registry= pointing to a private feed if testing prereleases.
  • If you need unreleased code, reference a Git tag/commit: "@kixelated/hang": "github:kixelated/moq#<commit-or-tag>:js/hang" (ensure your build environment has access).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"@kixelated/hang": "link:../moq/js/hang",
"@kixelated/hang": "^0.3.9",
🤖 Prompt for AI Agents
In package.json around line 16, the dependency "@kixelated/hang":
"link:../moq/js/hang" is a local filesystem link that will break CI/Cloudflare
builds; replace it with a published semver or a remote Git reference for
reproducible builds (e.g. a package version or
"github:kixelated/moq#<commit-or-tag>:js/hang"), and keep any local link only as
a developer-only override (use pnpm link locally or per-developer .npmrc) so
commits do not include link: paths that remote builders can't resolve.

Comment on lines 12 to 13
<hang-support prop:mode="publish" prop:show="partial" />

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Same TSX custom element typing issue as watch.tsx.

The hang-* tags need the global JSX declaration to pass type checking. See suggested src/types/hang-elements.d.ts.

Also applies to: 21-34

🧰 Tools
🪛 GitHub Check: check

[failure] 12-12:
Property 'hang-support' does not exist on type 'JSX.IntrinsicElements'.

🪛 GitHub Actions: pr

[error] 12-12: TypeScript error TS2339: Property 'hang-support' does not exist on type 'JSX.IntrinsicElements'.

🤖 Prompt for AI Agents
In src/components/publish.tsx around lines 12 to 13 (and also covering lines
21-34), the custom JSX tag <hang-support> is not typed so TypeScript fails type
checking; add a global JSX declaration file (e.g., src/types/hang-elements.d.ts)
that augments the JSX.IntrinsicElements with entries for "hang-support" (and any
other hang-* tags used on lines 21-34) specifying appropriate props (e.g.,
mode?: string; show?: string; [key: string]: any), export it as a global
declaration (declare global { namespace JSX { interface IntrinsicElements {
"hang-support": any; /* or specific props */ /* add other hang-* tags here */ }
} }) and ensure this file is included by tsconfig.json so the compiler picks up
the custom element typings.

Comment on lines +22 to +26
[hang.live](https://hang.live) is place to hang out with friends after a long day.
It's meant to be fun and stupid.
Everything is **live** and focused on human interaction.
People will *eventually* need a break from AI slop... even if that means disqualifying myself from VC funding.

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix a couple of public-facing grammar nits.

Improves polish without changing tone.

-[hang.live](https://hang.live) is place to hang out with friends after a long day.
+[hang.live](https://hang.live) is a place to hang out with friends after a long day.
@@
-The vibe coded UI is closed source for now.
+The vibe-coded UI is closed source for now.

Also applies to: 51-54

🧰 Tools
🪛 LanguageTool

[grammar] ~22-~22: There might be a mistake here.
Context: ...igure> hang.live is place to hang out with friends after a ...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ... hang out with friends after a long day. It's meant to be fun and stupid. Everyth...

(QB_NEW_EN)

🤖 Prompt for AI Agents
In src/pages/blog/first-app.mdx around lines 22–26 (and also apply the same
fixes to lines 51–54): correct the public-facing grammar by inserting the
missing article ("is place" -> "is a place") and ensure punctuation/spacing is
consistent (single spaces after sentences and terminal periods where
appropriate) while preserving the original tone and emphasis.

@kixelated kixelated merged commit 72562b7 into main Sep 4, 2025
1 of 2 checks passed
@kixelated kixelated deleted the cloudflare branch September 4, 2025 23:56
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.

2 participants