diff --git a/.agents/skills b/.agents/skills new file mode 120000 index 0000000..454b842 --- /dev/null +++ b/.agents/skills @@ -0,0 +1 @@ +../.claude/skills \ No newline at end of file diff --git a/.claude/hooks/direnv.sh b/.claude/hooks/direnv.sh new file mode 100755 index 0000000..184afc9 --- /dev/null +++ b/.claude/hooks/direnv.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# Load the project's direnv/nix dev shell into the Claude Code session so Bash +# tool commands resolve flake-pinned tools (bun, nixfmt, ...) instead of system +# ones. No-op for anyone without direnv or an .envrc, so non-nix setups are +# unaffected. +# +# Claude Code inlines $CLAUDE_ENV_FILE into the command string of every Bash +# call, and Linux caps a single argv entry at MAX_ARG_STRLEN, 128KB. This dev +# shell exports around 190KB, so the environment goes in a sidecar file and +# $CLAUDE_ENV_FILE gets only a line sourcing it. macOS has no per-argument cap, +# which is why inlining the whole environment appeared to work there. + +set -eu + +MARKER='# moq.dev dev shell' + +# Nothing to export into without this; older Claude Code versions won't set it. +[ -n "${CLAUDE_ENV_FILE:-}" ] || exit 0 + +command -v direnv >/dev/null 2>&1 || exit 0 + +# Require an explicit project dir so we never approve/export a stray .envrc from +# some unrelated working directory. +[ -n "${CLAUDE_PROJECT_DIR:-}" ] || exit 0 +cd "$CLAUDE_PROJECT_DIR" || exit 0 +[ -f .envrc ] || exit 0 + +# Clear any inherited direnv state so the dev shell is recomputed in full. +# Without this, a stale DIRENV_DIFF from the parent process makes direnv assume +# the env is already loaded and emit nothing. +unset DIRENV_DIR DIRENV_DIFF DIRENV_WATCHES DIRENV_FILE DIRENV_LAYOUT + +direnv allow . 2>/dev/null || exit 0 + +raw=$(mktemp) || exit 1 +scratch='' +trap 'rm -f "$raw"; [ -z "$scratch" ] || rm -f "$scratch"' EXIT + +# Clear inherited variables before restoring the snapshot, including variables +# that .envrc removed. Otherwise later shells inherit those removed values. +env -0 >"$raw" +inherited=() +while IFS= read -r -d '' entry; do + name=${entry%%=*} + [[ $name =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue + case $name in + DIRENV_* | PWD | OLDPWD | SHLVL | _ | SHELLOPTS | BASHOPTS | BASH_VERSINFO | UID | EUID | PPID) continue ;; + esac + inherited+=("$name") +done <"$raw" + +# Read the environment with `env -0` rather than a shell builtin: nixpkgs builds +# the non-interactive `bash` without programmable completion, so `compgen` does +# not exist inside the very dev shell this hook is here to load. +if ! direnv exec . env -0 >"$raw"; then + echo "direnv hook: could not load the dev shell, see the direnv output above." >&2 + exit 1 +fi + +if [ ! -s "$raw" ]; then + echo "direnv hook: the dev shell exported nothing, so no tools would resolve." >&2 + exit 1 +fi + +sidecar=$CLAUDE_ENV_FILE.direnv +# mktemp keeps credentials private and lets readers retain the old snapshot +# until the replacement has been written successfully. +scratch=$(mktemp "$sidecar.XXXXXX") + +{ + for name in "${inherited[@]}"; do + printf 'unset %s\n' "$name" + done + while IFS= read -r -d '' entry; do + name=${entry%%=*} + + # Skip exported functions (BASH_FUNC_foo%%) and anything else that is not a + # plain identifier, since `export` cannot restore those. + [[ $name =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue + + # direnv's bookkeeping, and the shell's own state, are not ours to restore. + case $name in + DIRENV_* | PWD | OLDPWD | SHLVL | _ | SHELLOPTS | BASHOPTS | BASH_VERSINFO | UID | EUID | PPID) continue ;; + esac + + printf 'export %s=%q\n' "$name" "${entry#*=}" + done <"$raw" +} >"$scratch" + +mv -f "$scratch" "$sidecar" + +# Refresh on every SessionStart, but install the sourcing line only once. +grep -qxF "$MARKER" "$CLAUDE_ENV_FILE" 2>/dev/null && exit 0 +printf '%s\n. %q\n' "$MARKER" "$sidecar" >>"$CLAUDE_ENV_FILE" diff --git a/.claude/rules/prompting.md b/.claude/rules/prompting.md new file mode 100644 index 0000000..e373e94 --- /dev/null +++ b/.claude/rules/prompting.md @@ -0,0 +1,9 @@ +--- +paths: + - "**/AGENTS.md" + - "CONTRIBUTING.md" + - "PROMPTING.md" + - ".claude/skills/**" +--- + +Before editing this file, read `PROMPTING.md` at the repository root and apply it. diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..10feb04 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-settings.json", + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/direnv.sh" + } + ] + } + ] + }, + "permissions": { + "allow": [ + "Bash(just *)", + "Bash(bun *)", + "Bash(bunx *)", + "Bash(quest *)", + "WebFetch(domain:moq.dev)", + "WebFetch(domain:doc.moq.dev)", + "WebFetch(domain:docs.astro.build)", + "WebFetch(domain:developers.cloudflare.com)" + ], + "ask": ["Bash(just deploy*)", "Bash(bun wrangler deploy*)", "Bash(bunx wrangler deploy*)"] + } +} diff --git a/.claude/skills/quest-audit/SKILL.md b/.claude/skills/quest-audit/SKILL.md new file mode 100644 index 0000000..8ae2c71 --- /dev/null +++ b/.claude/skills/quest-audit/SKILL.md @@ -0,0 +1,7 @@ +--- +name: quest-audit +description: Audit outstanding quests for disagreements, conflicts, misaligned priorities, and stale plans, then resolve them with /quest-plan. Use when the user invokes /quest-audit or asks to check the quest tree for consistency. +--- + +Run `quest skill audit` and follow its output. +If `quest` is not installed, follow https://github.com/kixelated/quest/blob/main/SETUP.md first. diff --git a/.claude/skills/quest-complete/SKILL.md b/.claude/skills/quest-complete/SKILL.md new file mode 100644 index 0000000..6c6bd4e --- /dev/null +++ b/.claude/skills/quest-complete/SKILL.md @@ -0,0 +1,7 @@ +--- +name: quest-complete +description: Decide which open PRs to merge, then merge them in parallel. +--- + +Run `quest skill complete` and follow its output. +If `quest` is not installed, follow https://github.com/kixelated/quest/blob/main/SETUP.md first. diff --git a/.claude/skills/quest-delete/SKILL.md b/.claude/skills/quest-delete/SKILL.md new file mode 100644 index 0000000..5d12f0c --- /dev/null +++ b/.claude/skills/quest-delete/SKILL.md @@ -0,0 +1,7 @@ +--- +name: quest-delete +description: Abandon a quest. +--- + +Run `quest skill delete` and follow its output. +If `quest` is not installed, follow https://github.com/kixelated/quest/blob/main/SETUP.md first. diff --git a/.claude/skills/quest-export/SKILL.md b/.claude/skills/quest-export/SKILL.md new file mode 100644 index 0000000..7f52f47 --- /dev/null +++ b/.claude/skills/quest-export/SKILL.md @@ -0,0 +1,7 @@ +--- +name: quest-export +description: Turn active quests into GitHub issues before leaving Quest. Use when the user invokes /quest-export or asks to export quests before removing Quest. +--- + +Run `quest skill export` and follow its output. +If `quest` is not installed, follow https://github.com/kixelated/quest/blob/main/SETUP.md first. diff --git a/.claude/skills/quest-import/SKILL.md b/.claude/skills/quest-import/SKILL.md new file mode 100644 index 0000000..fbd48cc --- /dev/null +++ b/.claude/skills/quest-import/SKILL.md @@ -0,0 +1,7 @@ +--- +name: quest-import +description: Import GitHub issues as quests. +--- + +Run `quest skill import` and follow its output. +If `quest` is not installed, follow https://github.com/kixelated/quest/blob/main/SETUP.md first. diff --git a/.claude/skills/quest-iterate/SKILL.md b/.claude/skills/quest-iterate/SKILL.md new file mode 100644 index 0000000..ce46524 --- /dev/null +++ b/.claude/skills/quest-iterate/SKILL.md @@ -0,0 +1,7 @@ +--- +name: quest-iterate +description: Iterate on an open PR to settle its open decisions, fix CI and review findings, and push. +--- + +Run `quest skill iterate` and follow its output. +If `quest` is not installed, follow https://github.com/kixelated/quest/blob/main/SETUP.md first. diff --git a/.claude/skills/quest-merge/SKILL.md b/.claude/skills/quest-merge/SKILL.md new file mode 100644 index 0000000..b665c4f --- /dev/null +++ b/.claude/skills/quest-merge/SKILL.md @@ -0,0 +1,7 @@ +--- +name: quest-merge +description: Merge a quest once reviews and CI pass. +--- + +Run `quest skill merge` and follow its output. +If `quest` is not installed, follow https://github.com/kixelated/quest/blob/main/SETUP.md first. diff --git a/.claude/skills/quest-plan/SKILL.md b/.claude/skills/quest-plan/SKILL.md new file mode 100644 index 0000000..7b03073 --- /dev/null +++ b/.claude/skills/quest-plan/SKILL.md @@ -0,0 +1,7 @@ +--- +name: quest-plan +description: Scope and create quests through an interactive interview. Use when the user invokes /quest-plan, asks to plan a quest, or wants unsettled work split into quests. +--- + +Run `quest skill plan` and follow its output. +If `quest` is not installed, follow https://github.com/kixelated/quest/blob/main/SETUP.md first. diff --git a/.claude/skills/quest-spawn/SKILL.md b/.claude/skills/quest-spawn/SKILL.md new file mode 100644 index 0000000..21a9b33 --- /dev/null +++ b/.claude/skills/quest-spawn/SKILL.md @@ -0,0 +1,7 @@ +--- +name: quest-spawn +description: Start multiple quests in parallel. +--- + +Run `quest skill spawn` and follow its output. +If `quest` is not installed, follow https://github.com/kixelated/quest/blob/main/SETUP.md first. diff --git a/.claude/skills/quest-start/SKILL.md b/.claude/skills/quest-start/SKILL.md new file mode 100644 index 0000000..5164b67 --- /dev/null +++ b/.claude/skills/quest-start/SKILL.md @@ -0,0 +1,7 @@ +--- +name: quest-start +description: Start work on a quest. +--- + +Run `quest skill start` and follow its output. +If `quest` is not installed, follow https://github.com/kixelated/quest/blob/main/SETUP.md first. diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index c5a35aa..651fecf 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -34,3 +34,5 @@ jobs: - uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31 # Evaluating the shell checks the Nix, package.json, and Docker Bun pins. - run: nix eval --raw .#devShells.x86_64-linux.default.drvPath + # Just the quest package, not the whole dev shell. + - run: nix run .#quest -- check diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..8d3dc03 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,164 @@ +# AGENTS.md + +## Project Overview + +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 `` publisher, no framework | +| `moq.watch` | `sites/watch` | A bare `` player, no framework | + +## Essential Commands + +```bash +# Development +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 of all three sites +just deploy # Deploy all three to Cloudflare (staging by default) +just deploy live # Deploy to production +just prod # Build and preview production locally + +# Code Quality +just check # Run Biome linting and TypeScript checking +just fix # Auto-fix code formatting/lint issues +``` + +## Required + +- Dig into the root cause and fix it at the source. Never work around a fixable bug with a retry, sleep, or timeout. +- Fail loud and early. Error on unsupported or malformed input rather than warn and continue. +- Reproduce bugs before fixing them. Land each fix with a regression test that fails without it, when one is easy. +- Keep the PR focused. No unrelated refactors, formatting churn, or drive-by changes; split when in doubt. +- Refactor aggressively for long-term maintainability, but re-evaluate the direction as you learn. +- Propose a course change, even suggest abandoning a PR, rather than finish a half-solution. +- When a decision is the maintainer's (design, copy, naming, scope), ask with 2-3 options and a recommendation. +- Never deploy to any environment, including staging, without the user's explicit permission for that deployment. A coding task or quest does not authorize a deploy. +- Protocol, player, and publisher fixes belong upstream in moq-dev/moq (`@moq/publish`, `@moq/watch`, ...). Fix them there first, then bump the package here. +- Never edit an `AGENTS.md`, `PROMPTING.md`, or skill without being prompted. Read `PROMPTING.md` before editing one or instructing other agents via context, memory, plans, or reviews. +- No em dashes in code, comments, or agent-written prose. +- Match the existing conventions, patterns, and naming when possible. +- Fix any outdated docs and comments inline; don't add a separate PR for it. +- Add the AI marker `(Written by )` to any posts on GitHub, excluding commit messages that contain `Co-Authored-By:` trailers. +- Any AI comments may be challenged, and not confused with human maintainers. + +## Guidelines + +- Comments should explain the non-obvious why, and never the history. +- Inline simple helpers. +- Question whether functionality is needed at all before adding it. +- Deleting code is better than adding code. +- Prefer the simple solution. +- New dependencies use the newest stable version. +- Say it once, in the fewest words that hold up. +- Suggest follow up sessions and quests when finished, interactively prompting the user. +- Prefer a quest over a GitHub issue for work needing durable scope. +- Try to do stuff asynchronously. ex. ask about follow-ups while checks run. +- If the core problem is addressed, ship it instead of spinning your wheels on meaningless revisions. + +## Development + +PRs target `main`. Before starting, `git fetch origin` and set the upstream to the base branch. +`git stash` is one stack shared by every worktree; set work aside with a commit instead. +Use the Nix dev shell (direnv, or `nix develop --command just ...`) so tooling matches CI. +Run `just fix` before committing. `just check` alone is not verification: look at the change in the dev server. +Quests: when work mentions a quest, run `quest guide` and follow it. The `quest` binary comes from the kixelated/quest flake input and serves the quest skills; change them upstream and bump the input. + +## Architecture Overview + +### Technology Stack +- **Framework**: Astro (static output) +- **UI Components**: Solid.js for interactive elements +- **Styling**: Tailwind CSS +- **Build**: Vite +- **Code Quality**: Biome for linting/formatting +- **Package Manager**: bun v1.3.13 +- **Task Runner**: just +- **Toolchain**: pinned in `flake.nix`; `nix develop` (or direnv, via `.envrc`) provides bun, node, and just + +The Bun version in nixpkgs must match `packageManager` in `package.json` +and the base image in `Dockerfile`. CI reads its version from `package.json` +and evaluates the flake assertions to catch drift. When a weekly Dependabot +Nix update changes Bun, update the other two pins in the same PR. + +### Key Components + +**MoQ Client Implementation** (`@moq/publish` + `@moq/watch` packages): + - Custom web components: ``, `` + - UI wrapper components: ``, `` + +### Important Patterns + +- **No REST APIs**: Uses WebTransport directly for streaming +- **Stateless**: No database or user management +- **Error Handling**: Component-level with `src/components/fail.tsx` +- **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: `//`, e.g. +`moq.pub/try/h2z15dgmdh.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/try/x.hang` and +watch it back at `moq.watch/try/x.hang`. + +Anything that *isn't* part of the broadcast's identity stays in the query +string: `?relay=`, `?cloudflare=`, `?jwt=`, and +(moq.pub only) `?source=camera`. `?cloudflare=draft-16` is shorthand for +`https://draft-16.cloudflare.mediaoverquic.com`; the project path segment still +carries Cloudflare's relay token. `cloudflare` and `relay` are mutually +exclusive. + +A bare `moq.pub/` mints a private broadcast in the `try` project from +`POST ${PUBLIC_API_URL}/try/token` and keeps the publish token in its own URL. +**A watch link never carries `?jwt=`**: moq tokens are prefix-scoped with +separate publish (`put`) and subscribe (`get`) grants, so a shared publish token +lets anyone publish. moq.pub links to moq.watch only for `try` on the default +relay, and moq.watch mints its own subscribe token from `/try/watch` for a +tokenless `try` path. Other projects get no link; swap the hostname. + +`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 `//`, and the relay URL +- `route.ts`: old `?project=&name=` links redirect into the path +- `try.ts`: mint `try` credentials from the API +- `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 and old `?project=&name=` links don't redirect. +- 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 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 ` builds all three sites in that mode and uploads each Worker. +The player sites read the repo-root `.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. + +## Development Tips + +- Broadcasts are ephemeral - no persistence layer +- The `@moq/publish` and `@moq/watch` packages handle all MoQ protocol implementation +- For new blog posts, add MDX files to `src/pages/blog/` +- Component changes in `src/components/` automatically reload with HMR diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 1eeaf3c..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,126 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -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 `` publisher, no framework | -| `moq.watch` | `sites/watch` | A bare `` player, no framework | - -## Essential Commands - -```bash -# Development -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 of all three sites -just deploy # Deploy all three to Cloudflare (staging by default) -just deploy live # Deploy to production -just prod # Build and preview production locally - -# Code Quality -just check # Run Biome linting and TypeScript checking -just fix # Auto-fix code formatting/lint issues -``` - -## Architecture Overview - -### Technology Stack -- **Framework**: Astro (static output) -- **UI Components**: Solid.js for interactive elements -- **Styling**: Tailwind CSS -- **Build**: Vite -- **Code Quality**: Biome for linting/formatting -- **Package Manager**: bun v1.3.13 -- **Task Runner**: just -- **Toolchain**: pinned in `flake.nix`; `nix develop` (or direnv, via `.envrc`) provides bun, node, and just - -The Bun version in nixpkgs must match `packageManager` in `package.json` -and the base image in `Dockerfile`. CI reads its version from `package.json` -and evaluates the flake assertions to catch drift. When a weekly Dependabot -Nix update changes Bun, update the other two pins in the same PR. - -### Key Components - -**MoQ Client Implementation** (`@moq/publish` + `@moq/watch` packages): - - Custom web components: ``, `` - - UI wrapper components: ``, `` - -### Important Patterns - -- **No REST APIs**: Uses WebTransport directly for streaming -- **Stateless**: No database or user management -- **Error Handling**: Component-level with `src/components/fail.tsx` -- **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: `//`, e.g. -`moq.pub/try/h2z15dgmdh.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/try/x.hang` and -watch it back at `moq.watch/try/x.hang`. - -Anything that *isn't* part of the broadcast's identity stays in the query -string: `?relay=`, `?cloudflare=`, `?jwt=`, and -(moq.pub only) `?source=camera`. `?cloudflare=draft-16` is shorthand for -`https://draft-16.cloudflare.mediaoverquic.com`; the project path segment still -carries Cloudflare's relay token. `cloudflare` and `relay` are mutually -exclusive. - -A bare `moq.pub/` mints a private broadcast in the `try` project from -`POST ${PUBLIC_API_URL}/try/token` and keeps the publish token in its own URL. -**A watch link never carries `?jwt=`**: moq tokens are prefix-scoped with -separate publish (`put`) and subscribe (`get`) grants, so a shared publish token -lets anyone publish. moq.pub links to moq.watch only for `try` on the default -relay, and moq.watch mints its own subscribe token from `/try/watch` for a -tokenless `try` path. Other projects get no link; swap the hostname. - -`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 `//`, and the relay URL -- `route.ts` — old `?project=&name=` links redirect into the path -- `try.ts` — mint `try` credentials from the API -- `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 and old `?project=&name=` links don't redirect. -- 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 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 ` builds all three sites in that mode and uploads each Worker. -The player sites read the repo-root `.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. - -## Development Tips - -- Broadcasts are ephemeral - no persistence layer -- The `@moq/publish` and `@moq/watch` packages handle all MoQ protocol implementation -- For new blog posts, add MDX files to `src/pages/blog/` -- Component changes in `src/components/` automatically reload with HMR diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/PROMPTING.md b/PROMPTING.md new file mode 100644 index 0000000..36f7956 --- /dev/null +++ b/PROMPTING.md @@ -0,0 +1,45 @@ +Read this before indirectly instructing agents. + +LLMs are not trained on how to write prompts. +They're trained on the outputs, not the inputs, and thus generate slop when chained. +So when you're drafting instructions for a future agent, please keep these rules in mind. + +# General + +**Do not over-prescribe**. +The facts will evolve over time. + +Focus on articulating the goal and/or root problems. +Keep things flexible so an agent can adjust as it digs deeper into the possible problems or solutions. + +You can (and should) include recommendations. +But focus on best-practices and things to look out for, not specific lines of code. +Mocking up an API makes a lot of sense but again, it should be flexible enough to adjust with the implementation. + +# Context + +This includes editing any `AGENTS.md`, `CONTRIBUTING.md`, etc. + +These files need to be light because they're loaded into every prompt. +More specific context (ex. per language or project) should exist in optional files/folders. + +These base prompts are meant to fix regular agent mistakes. +If there is a history of bad practices, or sub-optimal behavior, update `AGENTS.md` to steer away from them. +If you think a slight tweak to the base prompt would help, propose it. + +Focus on best practices and conventions. +Don't document the repository; the code and documentation can handle that. + +Before changing agent-instruction files, check the relevant official Claude/Codex guidance: + +- +- +- +- +- +- + +# Skills + +Skills are meant to avoid repeated duplicate prompts. +If the usage of a skill is unclear, offer to update to skill to reduce the ambiguity. diff --git a/flake.lock b/flake.lock index e9f34e2..7fd1bd1 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,20 @@ { "nodes": { + "crane": { + "locked": { + "lastModified": 1789760033, + "narHash": "sha256-jtT4yxZpR8seYnlCMMWSSPlFN92zO6ICuZ8pJrmi86k=", + "owner": "ipetkov", + "repo": "crane", + "rev": "73b980519cefc727a5f6cc8e5c0947a2f9be6edd", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems" @@ -34,10 +49,58 @@ "type": "github" } }, + "quest": { + "inputs": { + "crane": "crane", + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ], + "rust-overlay": "rust-overlay" + }, + "locked": { + "lastModified": 1791221651, + "narHash": "sha256-YTiITf1VEBkgBIADP24mzcFFLy1pL6DyREYS15+ri1c=", + "owner": "kixelated", + "repo": "quest", + "rev": "5ff9229d277a4580296795a82f427f5ec30072c7", + "type": "github" + }, + "original": { + "owner": "kixelated", + "repo": "quest", + "rev": "5ff9229d277a4580296795a82f427f5ec30072c7", + "type": "github" + } + }, "root": { "inputs": { "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "quest": "quest" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "quest", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1789889921, + "narHash": "sha256-aL/ogiN7qZCGeNovS1f9hX73jwYIKb4Pcq6AZm57WtY=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "1fb104a12a8667045559b2575d6d448ae2fbd99b", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" } }, "systems": { diff --git a/flake.nix b/flake.nix index dbe226d..4819eef 100644 --- a/flake.nix +++ b/flake.nix @@ -4,12 +4,21 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; flake-utils.url = "github:numtide/flake-utils"; + + # The quest CLI, which also serves the quest guide and skills the stubs in + # .claude/skills call. Bump the rev to upgrade them. + quest = { + url = "github:kixelated/quest/5ff9229d277a4580296795a82f427f5ec30072c7"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.flake-utils.follows = "flake-utils"; + }; }; outputs = { nixpkgs, flake-utils, + quest, ... }: flake-utils.lib.eachSystem @@ -22,8 +31,12 @@ system: let pkgs = import nixpkgs { inherit system; }; + quest-cli = quest.packages.${system}.default; in { + # The quest CLI alone, so CI can validate the tree without the dev shell. + packages.quest = quest-cli; + devShells.default = assert pkgs.lib.assertMsg ((builtins.fromJSON (builtins.readFile ./package.json)).packageManager == "bun@${pkgs.bun.version}") @@ -47,6 +60,9 @@ # The task runner every recipe in the justfile is written for. just + + # `quest check` and the quest-* skills; see the input. + quest-cli ]; # `just cards` imports Playwright from here rather than node_modules, diff --git a/justfile b/justfile index d3b3388..5b231ac 100644 --- a/justfile +++ b/justfile @@ -16,6 +16,9 @@ check: # Make sure Typescript compiles bun run check + # Validate the quest tree + quest check + # Automatically fix some issues. fix: # Fix the JS packages diff --git a/quest/README.md b/quest/README.md new file mode 100644 index 0000000..0251850 --- /dev/null +++ b/quest/README.md @@ -0,0 +1,18 @@ +# moq.dev questline + +## Goal + +Keep the living work on moq.dev, moq.pub, and moq.watch organized as visible, +versioned quests, grouped into milestones ordered by priority. + +## Plan + +This tree tracks the sites: the blog, the demos, moq.pub, and moq.watch. +Protocol, player, and publisher fixes are quests in +[moq-dev/moq](https://github.com/moq-dev/moq/tree/main/quest) and arrive here +only as a package bump. A draft blog post stays a draft PR, which already holds +its scope, until it is scheduled. + +## Required + +- [m0: immediate priorities](/quest/m0/README.md) - the @moq bump for draft-18 relays, the live deploy, and hosted worklets diff --git a/quest/m0/README.md b/quest/m0/README.md new file mode 100644 index 0000000..83ab15a --- /dev/null +++ b/quest/m0/README.md @@ -0,0 +1,20 @@ +# m0: immediate priorities + +## Goal + +The live sites run the current `@moq/*` packages: moq.watch plays from draft-18 +relays, moq.pub mints private `try` broadcasts instead of `/anon` names, and +the sites host the player worklets once a release ships `assets()`. + +## Plan + +As of 2026-10-09, live moq.pub still redirects a bare `/` to an invented +`/anon/...` name: the `try` integration (#136) passed staging but never +deployed live. The live deploy waits for the bump, so one deploy carries both. + +## Required + +- [Bump @moq to the draft-18 release](/quest/m0/moq-bump.md) - moq.watch decodes LARGEST_OBJECT and plays from draft-18 relays +- [Deploy live](/quest/m0/deploy-live.md) - the maintainer ships the bump and the try demos to moq.dev, moq.pub, and moq.watch +- [A release ships assets()](/quest/m0/assets-release.md) - the hosted worklet files exist on npm to copy +- [Dogfood hosted worklets](/quest/m0/dogfood-assets.md) - the sites serve the @moq worklets and workers themselves diff --git a/quest/m0/assets-release.md b/quest/m0/assets-release.md new file mode 100644 index 0000000..a51752d --- /dev/null +++ b/quest/m0/assets-release.md @@ -0,0 +1,17 @@ +# [XS] A release ships assets() + +## Goal + +The newest `@moq/watch` and `@moq/publish` on npm export `assets()` and ship +their worklet and worker files under `assets/`. + +This quest tracks a condition outside the repository. When it holds, delete +this quest and every `Required` entry that links it. + +## Plan + +As of 2026-10-09 moq `main` carries `assets()` (moq-dev/moq#4518) but the +newest releases (`@moq/watch` 0.6.2, `@moq/publish` 0.5.2) lack it. Check +`npm view @moq/watch version` for a newer version, then confirm its `exports` +lists `./assets/*`. moq tracks the release as +[its own condition quest](https://github.com/moq-dev/moq/blob/main/quest/m1/assets-release.md). diff --git a/quest/m0/deploy-live.md b/quest/m0/deploy-live.md new file mode 100644 index 0000000..adfec37 --- /dev/null +++ b/quest/m0/deploy-live.md @@ -0,0 +1,21 @@ +# [XS] Deploy live + +## Goal + +The maintainer runs `just deploy live`, and the live moq.dev, moq.pub, and +moq.watch serve the bump and the private `try` demos. Nothing deploys on +merge, and an agent never deploys without the maintainer's go-ahead. + +## Plan + +Waits for the bump so one deploy ships both (decided 2026-10-09). + +Check after deploying: a bare `moq.pub/` mints a `try` broadcast instead of +redirecting to `/anon/...`; publish from moq.dev's `/publish` and from +moq.pub, open the share link in a private window, confirm it plays, and that +the same path is refused tokenless. moq.pro tracks the same check as +[its condition quest](https://github.com/moq-dev/moq.pro/blob/main/quest/m1/try/moq-dev-live.md). + +## Required + +- [Bump @moq to the draft-18 release](/quest/m0/moq-bump.md) - the deploy carries the #166 fix diff --git a/quest/m0/dogfood-assets.md b/quest/m0/dogfood-assets.md new file mode 100644 index 0000000..bcce16a --- /dev/null +++ b/quest/m0/dogfood-assets.md @@ -0,0 +1,29 @@ +# [S] Dogfood hosted worklets + +## Goal + +moq.dev and the `sites/pub` and `sites/watch` Vite sites host the `@moq` +worklet and worker files and call `assets()`, so the hosted path gets real +traffic. CSP is out of scope: no site adds a policy. + +## Plan + +Moved from [moq's quest](https://github.com/moq-dev/moq/blob/main/quest/m1/dogfood-assets.md), +which keeps the moq.pro dashboard half (2026-10-09). The maintainer wants +hosted mode dogfooded, not just the pin bump. + +Guidance: + +- Copy `node_modules/@moq/{watch,publish}/assets/*` into a served `/moq/` + directory at build time. `vite-plugin-static-copy` is already an unused + devDependency; Astro takes it through `vite.plugins`. +- Call `assets("/moq/")` once, before any element or player starts. The sites + import the element entries, so import `assets` from the package root. +- Verify in a browser that audio plays and publishes, and that the network + panel shows the worklets and the capture worker (Firefox) loading from + `/moq/`, not `blob:`. + +## Required + +- [A release ships assets()](/quest/m0/assets-release.md) - the hosted files exist on npm to copy +- [Bump @moq to the draft-18 release](/quest/m0/moq-bump.md) - lands the API break first, so this only adds assets() diff --git a/quest/m0/moq-bump.md b/quest/m0/moq-bump.md new file mode 100644 index 0000000..a5e356a --- /dev/null +++ b/quest/m0/moq-bump.md @@ -0,0 +1,21 @@ +# [S] Bump @moq to the draft-18 release + +## Goal + +The sites build against `@moq/watch` 0.6, `@moq/publish` 0.5, and `@moq/boy` +0.4 or newer, so moq.watch decodes a draft-18 SUBSCRIBE_OK's LARGEST_OBJECT +(fixed in moq-dev/moq#3561, first in `@moq/net` 0.4.0) and plays. + +## Plan + +The current `^0.5.4` / `^0.4.7` / `^0.3.1` ranges cannot resolve to these +minors, and each is a breaking release, so adapt the call sites to whatever +API moved. Bump all three together so they share one `@moq/net`. + +Verify in the dev servers (`just dev`, `just dev-pub`, `just dev-watch`): +publish and watch back a `try` broadcast, and play from a draft-18 relay if +one is reachable. + +## Closes + +- [#166](https://github.com/moq-dev/moq.dev/issues/166) - moq.watch cannot play from draft-18 relays