diff --git a/.dockerignore b/.dockerignore
deleted file mode 100644
index 85dcc16d..00000000
--- a/.dockerignore
+++ /dev/null
@@ -1,2 +0,0 @@
-.git
-node_modules
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index d8442e42..23810b1e 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -10,6 +10,13 @@ jobs:
steps:
- uses: actions/checkout@v7
+ with:
+ submodules: true
+ # bun install builds @moq/libav from the moq submodule's flake. Accepting
+ # its nixConfig pulls the build from the nix.moq.dev cache.
+ - uses: cachix/install-nix-action@448cde739abec1eaf0646249e3d25bb8d9bc7702 # v31
+ with:
+ extra_nix_config: accept-flake-config = true
# Read the Bun version from package.json.
- uses: oven-sh/setup-bun@v2
with:
@@ -32,7 +39,7 @@ jobs:
steps:
- uses: actions/checkout@v7
- uses: cachix/install-nix-action@448cde739abec1eaf0646249e3d25bb8d9bc7702 # v31
- # Evaluating the shell checks the Nix, package.json, and Docker Bun pins.
+ # Evaluating the shell checks the Nix and package.json 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/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..397a67aa
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "moq"]
+ path = moq
+ url = https://github.com/moq-dev/moq
diff --git a/AGENTS.md b/AGENTS.md
index 9659031c..be9e6d05 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -41,7 +41,7 @@ just fix # Auto-fix code formatting/lint issues
- 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.
+- Protocol, player, and publisher fixes belong upstream in moq-dev/moq (`@moq/publish`, `@moq/watch`, ...). Fix them there first, then bump the `moq` submodule here; the `@moq/*` packages build from it.
- 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.
@@ -83,10 +83,10 @@ Quests: when work mentions a quest, run `quest guide` and follow it. The `quest`
- **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.
+The Bun version in nixpkgs must match `packageManager` in `package.json`.
+CI reads its version from `package.json` and evaluates the flake assertion to
+catch drift. When a weekly Dependabot Nix update changes Bun, update
+`package.json` in the same PR.
### Key Components
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index 650362d5..00000000
--- a/Dockerfile
+++ /dev/null
@@ -1,18 +0,0 @@
-FROM oven/bun:1.4.2-slim AS base
-ENV NODE_ENV=production
-
-COPY . /app
-WORKDIR /app
-
-FROM base AS prod-deps
-RUN --mount=type=cache,id=bun,target=/root/.bun/install/cache bun install --production --frozen-lockfile
-
-FROM base AS build
-RUN --mount=type=cache,id=bun,target=/root/.bun/install/cache bun install --frozen-lockfile
-RUN bun run build
-
-FROM base
-COPY --from=prod-deps /app/node_modules /app/node_modules
-COPY --from=build /app/dist /app/dist
-ENV HOST="0.0.0.0"
-CMD [ "bun", "./dist/server/entry.mjs" ]
\ No newline at end of file
diff --git a/README.md b/README.md
index 89a57a9b..87e712d1 100644
--- a/README.md
+++ b/README.md
@@ -44,9 +44,12 @@ With [direnv](https://direnv.net/) installed, `direnv allow` loads that shell
automatically on every `cd` into the repo.
Don't have Nix? Install `bun`, Node.js 24, and `just` yourself, matching the `bun`
-version in `packageManager`. Either way, the dependencies come from `bun`:
+version in `packageManager`. You still need `nix` on your `PATH`: the `@moq/*`
+packages build from the `moq` submodule, and its `@moq/libav` is built with Nix.
+Either way, fetch the submodule and install with `bun`:
```bash
+git submodule update --init
bun i
```
diff --git a/astro.config.ts b/astro.config.ts
index 9371b6da..491ae8a7 100644
--- a/astro.config.ts
+++ b/astro.config.ts
@@ -1,10 +1,9 @@
-import fs from "node:fs";
-import path from "node:path";
import mdx from "@astrojs/mdx";
import solidJs from "@astrojs/solid-js";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "astro/config";
import { createCssVariablesTheme } from "shiki";
+import { worklet } from "./moq/js/common/vite-plugin-worklet.ts";
// Colors come from the --astro-code-* variables in global.css, shared with highlight.js.
const codeTheme = createCssVariablesTheme({ variablePrefix: "--astro-code-" });
@@ -20,20 +19,10 @@ export default defineConfig({
},
integrations: [mdx(), solidJs()],
vite: {
- plugins: [tailwindcss()],
+ plugins: [tailwindcss(), worklet()],
build: {
target: "esnext",
},
- server: {
- fs: {
- allow: [
- ".",
- // Allow `npm link @moq/*`
- fs.realpathSync(path.resolve("node_modules/@moq/publish")),
- fs.realpathSync(path.resolve("node_modules/@moq/watch")),
- ],
- },
- },
resolve: {
alias: {
"@": "/src",
diff --git a/biome.json b/biome.json
index 8cc88be0..89029838 100644
--- a/biome.json
+++ b/biome.json
@@ -7,7 +7,7 @@
},
"files": {
"ignoreUnknown": false,
- "includes": ["**", "!**/*.svg"]
+ "includes": ["**", "!**/*.svg", "!moq"]
},
"formatter": {
"enabled": true,
diff --git a/bun.lock b/bun.lock
index 568fa86f..b99a4bc4 100644
--- a/bun.lock
+++ b/bun.lock
@@ -5,10 +5,10 @@
"": {
"name": "moq.dev",
"dependencies": {
- "@moq/boy": "^0.4.1",
- "@moq/json": "^0.4.3",
- "@moq/publish": "^0.5.3",
- "@moq/watch": "^0.6.6",
+ "@moq/boy": "workspace:*",
+ "@moq/json": "workspace:*",
+ "@moq/publish": "workspace:*",
+ "@moq/watch": "workspace:*",
"astro": "^7.4.0",
"highlight.js": "^11.12.0",
"solid-js": "^1.9.17",
@@ -23,6 +23,7 @@
"@tailwindcss/vite": "^4.3.3",
"@types/bun": "^1.4.3",
"@types/node": "^26.6.5",
+ "rolldown": "^1.2.14",
"shiki": "^4.5.0",
"tailwindcss": "^4.3.3",
"typescript": "^7.0.2",
@@ -31,6 +32,260 @@
"wrangler": "^4.151.1",
},
},
+ "moq/js/clock": {
+ "name": "@moq/clock",
+ "version": "0.1.0",
+ "devDependencies": {
+ "@types/bun": "^1.4.3",
+ "rimraf": "^6.1.3",
+ "typescript": "7.0.2",
+ },
+ },
+ "moq/js/flate": {
+ "name": "@moq/flate",
+ "version": "0.1.3",
+ "dependencies": {
+ "@moq/net": "workspace:^",
+ "@moq/signals": "workspace:^",
+ "pako": "^3.0.2",
+ },
+ "devDependencies": {
+ "@moq/clock": "workspace:^",
+ "@types/bun": "^1.4.3",
+ "fflate": "^0.8.2",
+ "rimraf": "^6.1.3",
+ "typescript": "7.0.2",
+ },
+ },
+ "moq/js/hang": {
+ "name": "@moq/hang",
+ "version": "0.5.2",
+ "dependencies": {
+ "@kixelated/libavjs-webcodecs-polyfill": "^0.5.6",
+ "@moq/clock": "workspace:^",
+ "@moq/json": "workspace:^",
+ "@moq/libav": "workspace:^",
+ "@moq/loc": "workspace:^",
+ "@moq/net": "workspace:^",
+ "@moq/signals": "workspace:^",
+ "@zod/mini": "^4.6.7",
+ "zod": "^4.6.7",
+ },
+ "devDependencies": {
+ "@types/bun": "^1.4.3",
+ "@typescript/lib-dom": "npm:@types/web@^0.0.358",
+ "rimraf": "^6.1.3",
+ "typescript": "7.0.2",
+ },
+ },
+ "moq/js/json": {
+ "name": "@moq/json",
+ "version": "0.4.2",
+ "dependencies": {
+ "@moq/flate": "workspace:^",
+ "@moq/net": "workspace:^",
+ },
+ "devDependencies": {
+ "@moq/clock": "workspace:^",
+ "@types/bun": "^1.4.3",
+ "fast-check": "^4.10.2",
+ "rimraf": "^6.1.3",
+ "typescript": "7.0.2",
+ },
+ "peerDependencies": {
+ "@zod/mini": "^4.5.0",
+ "zod": "^4.5.0",
+ },
+ },
+ "moq/js/libav": {
+ "name": "@moq/libav",
+ "version": "0.1.0",
+ "dependencies": {
+ "@libav.js/types": "^6.10.9",
+ },
+ "devDependencies": {
+ "@types/bun": "^1.4.3",
+ "typescript": "7.0.2",
+ },
+ },
+ "moq/js/loc": {
+ "name": "@moq/loc",
+ "version": "0.2.4",
+ "dependencies": {
+ "@moq/clock": "workspace:^",
+ "@moq/net": "workspace:^",
+ },
+ "devDependencies": {
+ "@types/bun": "^1.4.3",
+ "rimraf": "^6.1.3",
+ "typescript": "7.0.2",
+ },
+ },
+ "moq/js/moq-boy": {
+ "name": "@moq/boy",
+ "version": "0.4.1",
+ "dependencies": {
+ "@moq/clock": "workspace:^",
+ "@moq/json": "workspace:^",
+ "@moq/net": "workspace:^",
+ "@moq/signals": "workspace:^",
+ "@moq/watch": "workspace:^",
+ "@zod/mini": "^4.6.7",
+ "zod": "^4.6.7",
+ },
+ "devDependencies": {
+ "@types/bun": "^1.4.3",
+ "esbuild": "^0.28.2",
+ "rimraf": "^6.1.3",
+ "solid-js": "^1.9.17",
+ "typescript": "7.0.2",
+ "vite": "^8.3.4",
+ "vite-plugin-solid": "^2.11.14",
+ },
+ },
+ "moq/js/msf": {
+ "name": "@moq/msf",
+ "version": "0.3.1",
+ "dependencies": {
+ "@moq/net": "workspace:^",
+ "@zod/mini": "^4.6.7",
+ "zod": "^4.6.7",
+ },
+ "devDependencies": {
+ "rimraf": "^6.1.3",
+ "typescript": "7.0.2",
+ },
+ },
+ "moq/js/net": {
+ "name": "@moq/net",
+ "version": "0.4.2",
+ "dependencies": {
+ "@moq/clock": "workspace:^",
+ "@moq/pattern": "workspace:^",
+ "@moq/qmux": "workspace:^",
+ "@moq/signals": "workspace:*",
+ "async-mutex": "^0.5.0",
+ "pako": "^3.0.2",
+ "uuid": "^14.0.3",
+ },
+ "devDependencies": {
+ "@types/bun": "^1.4.3",
+ "@types/node": "^26.6.4",
+ "@typescript/lib-dom": "npm:@types/web@^0.0.358",
+ "rimraf": "^6.1.3",
+ "typescript": "7.0.2",
+ },
+ "peerDependencies": {
+ "@zod/mini": "^4.5.0",
+ "zod": "^4.5.0",
+ },
+ },
+ "moq/js/pattern": {
+ "name": "@moq/pattern",
+ "version": "0.2.1",
+ "devDependencies": {
+ "@types/bun": "^1.4.3",
+ "rimraf": "^6.1.3",
+ "typescript": "7.0.2",
+ },
+ },
+ "moq/js/publish": {
+ "name": "@moq/publish",
+ "version": "0.5.2",
+ "dependencies": {
+ "@moq/clock": "workspace:^",
+ "@moq/hang": "workspace:^",
+ "@moq/json": "workspace:^",
+ "@moq/net": "workspace:^",
+ "@moq/signals": "workspace:^",
+ "@moq/video": "workspace:^",
+ "mediabunny": "^1.61.4",
+ },
+ "devDependencies": {
+ "@types/audioworklet": "^0.0.100",
+ "@types/bun": "^1.4.3",
+ "@typescript/lib-dom": "npm:@types/web@^0.0.358",
+ "rimraf": "^6.1.3",
+ "rolldown": "^1.2.13",
+ "typescript": "7.0.2",
+ "vite": "^8.3.4",
+ },
+ },
+ "moq/js/qmux": {
+ "name": "@moq/qmux",
+ "version": "0.3.3",
+ "dependencies": {
+ "@moq/clock": "workspace:^",
+ },
+ "devDependencies": {
+ "@types/bun": "^1.4.3",
+ "@typescript/lib-dom": "npm:@types/web@^0.0.358",
+ "rimraf": "^6.1.3",
+ "typescript": "7.0.2",
+ },
+ },
+ "moq/js/signals": {
+ "name": "@moq/signals",
+ "version": "0.2.5",
+ "dependencies": {
+ "@moq/clock": "workspace:^",
+ },
+ "devDependencies": {
+ "@types/bun": "^1.4.3",
+ "@types/react": "^19.3.0",
+ "fast-check": "^4.10.2",
+ "react": "^19.3.1",
+ "rimraf": "^6.1.3",
+ "solid-js": "^1.9.17",
+ "typescript": "7.0.2",
+ },
+ "peerDependencies": {
+ "@types/react": "^19.2.17",
+ "react": "^19.3.1",
+ "solid-js": "^1.9.15",
+ },
+ "optionalPeers": [
+ "@types/react",
+ "react",
+ "solid-js",
+ ],
+ },
+ "moq/js/video": {
+ "name": "@moq/video",
+ "version": "0.1.0",
+ "dependencies": {
+ "@moq/signals": "workspace:^",
+ },
+ "devDependencies": {
+ "@types/bun": "^1.4.3",
+ "@typescript/lib-dom": "npm:@types/web@^0.0.358",
+ "rimraf": "^6.1.3",
+ "typescript": "7.0.2",
+ },
+ },
+ "moq/js/watch": {
+ "name": "@moq/watch",
+ "version": "0.6.2",
+ "dependencies": {
+ "@moq/clock": "workspace:^",
+ "@moq/hang": "workspace:^",
+ "@moq/json": "workspace:^",
+ "@moq/msf": "workspace:^",
+ "@moq/net": "workspace:^",
+ "@moq/signals": "workspace:^",
+ "@moq/video": "workspace:^",
+ "media-captions": "0.0.18",
+ },
+ "devDependencies": {
+ "@types/audioworklet": "^0.0.100",
+ "@types/bun": "^1.4.3",
+ "@typescript/lib-dom": "npm:@types/web@^0.0.358",
+ "rimraf": "^6.1.3",
+ "rolldown": "^1.2.13",
+ "typescript": "7.0.2",
+ "vite": "^8.3.4",
+ },
+ },
},
"packages": {
"@astrojs/compiler-binding": ["@astrojs/compiler-binding@0.5.2", "", { "optionalDependencies": { "@astrojs/compiler-binding-android-arm64": "0.5.2", "@astrojs/compiler-binding-darwin-arm64": "0.5.2", "@astrojs/compiler-binding-darwin-x64": "0.5.2", "@astrojs/compiler-binding-linux-arm64-gnu": "0.5.2", "@astrojs/compiler-binding-linux-arm64-musl": "0.5.2", "@astrojs/compiler-binding-linux-x64-gnu": "0.5.2", "@astrojs/compiler-binding-linux-x64-musl": "0.5.2", "@astrojs/compiler-binding-wasm32-wasi": "0.5.2", "@astrojs/compiler-binding-win32-arm64-msvc": "0.5.2", "@astrojs/compiler-binding-win32-x64-msvc": "0.5.2" } }, "sha512-lEn76+0JcMQ5RDXU0wSMWJg2ZppiZvSULpghC29cfv4o9bhvWFDbQjsCtvjjXN9iVLFQ7fL3wTR2L2UU13mP8g=="],
@@ -287,37 +542,39 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
- "@kixelated/libavjs-webcodecs-polyfill": ["@kixelated/libavjs-webcodecs-polyfill@0.5.5", "", { "dependencies": { "@libav.js/types": "^6.7.7", "@ungap/global-this": "^0.4.4" } }, "sha512-Q1zgnTMMQ2F7IE9ylx3C1XzVbg5vYN18jiDINO5U3kNPBOHdYuUlJsMhtBoqr1M6ocLtoiqdHmLs7tHFgrw5KA=="],
+ "@kixelated/libavjs-webcodecs-polyfill": ["@kixelated/libavjs-webcodecs-polyfill@0.5.6", "", { "dependencies": { "@libav.js/types": "^6.7.7", "@ungap/global-this": "^0.4.4" } }, "sha512-A6FBsEAT/t0cDDaxo3h5XabiHX2RtycQbWV+MNKUghmctrXU1ygl05i3TTtnSF/eLmCLLO8xBMrXO+eGnpLWFw=="],
+
+ "@libav.js/types": ["@libav.js/types@6.10.9", "", {}, "sha512-AdngxD/VO8OI6/w7wlJC77kuFcsSvCUuo2RuC6gGeSW2DEvbzgBo2JtFLV6UWZp/tRNZhKm6jS0Tv8gwCKBHLg=="],
- "@libav.js/types": ["@libav.js/types@6.8.8", "", {}, "sha512-Lbik/0Q3x2R8cI7mOtRgt+nUWLqGXh7UinMndmpdXSDY4YEjYyVUDsq6fxkuriL78+LCYx8frZIN1r+oDsvYCQ=="],
+ "@moq/boy": ["@moq/boy@workspace:moq/js/moq-boy"],
- "@libav.js/variant-opus-af": ["@libav.js/variant-opus-af@6.10.9", "", {}, "sha512-yULJUCQIHDtwDPbT38t9pP/7EGgqwynVcOau46hZjtCe8TWMp67DTi2X0Emr2M4Ci3nzxNPf25IzO+IXSmHDBg=="],
+ "@moq/clock": ["@moq/clock@workspace:moq/js/clock"],
- "@moq/boy": ["@moq/boy@0.4.1", "", { "dependencies": { "@moq/json": "^0.4.1", "@moq/net": "^0.4.1", "@moq/signals": "^0.2.5", "@moq/watch": "^0.6.1", "@zod/mini": "^4.6.5", "zod": "^4.6.5" } }, "sha512-bFgyHgVrEPFaBnQuUpT3kWSjO6bCmNypEy3RaO666znCRnmGAHLMeDjOHKbT/ERXoGRuGyM9fM+oY4ENsvGNVw=="],
+ "@moq/flate": ["@moq/flate@workspace:moq/js/flate"],
- "@moq/flate": ["@moq/flate@0.1.3", "", { "dependencies": { "pako": "^3.0.1" } }, "sha512-7vXSIozvt6hSYCYWRFoReYl8p+fqpSdCsO5imxjqslhpc/zl071aS732Jt/tJ441iqAQBS86FZQuP+xGb0j4Vw=="],
+ "@moq/hang": ["@moq/hang@workspace:moq/js/hang"],
- "@moq/hang": ["@moq/hang@0.5.4", "", { "dependencies": { "@kixelated/libavjs-webcodecs-polyfill": "^0.5.5", "@libav.js/variant-opus-af": "^6.10.9", "@moq/json": "^0.4.3", "@moq/loc": "^0.2.4", "@moq/net": "^0.4.5", "@moq/signals": "^0.2.6", "@svta/cml-iso-bmff": "^1.0.7", "@svta/cml-utils": "^1.6.2", "@zod/mini": "^4.6.5", "zod": "^4.6.5" } }, "sha512-oHRqFBF1P5O1TVpFmxqWRM6Nkltnu8jzvybk/nIBSyMbUYeFWbbiWaWNDxXu3jeXX7ohm01oh5kkrwT/+bQ1uw=="],
+ "@moq/json": ["@moq/json@workspace:moq/js/json"],
- "@moq/json": ["@moq/json@0.4.3", "", { "dependencies": { "@moq/flate": "^0.1.3", "@moq/net": "^0.4.5", "@moq/signals": "^0.2.6" }, "peerDependencies": { "@zod/mini": "^4.5.0", "zod": "^4.5.0" } }, "sha512-HTfLRnuvA5sWvLzXjsfAfrdAUJiLG8FyiWUGmWR598kPq+S2SNJ3gK1uoUWeb/+z/fgEY/kdrg1BHv+TmAz/3g=="],
+ "@moq/libav": ["@moq/libav@workspace:moq/js/libav"],
- "@moq/loc": ["@moq/loc@0.2.4", "", { "dependencies": { "@moq/net": "^0.4.2" } }, "sha512-cwe0y+j7mfIuC+DOrfdLB1k5YU2+/xIfhnTv8VEiKLM5ZyfOZHblK3UwnrKqChcKkGFt3FkedRWFLMDIt9GCuA=="],
+ "@moq/loc": ["@moq/loc@workspace:moq/js/loc"],
- "@moq/msf": ["@moq/msf@0.3.1", "", { "dependencies": { "@moq/net": "^0.4.2", "@zod/mini": "^4.6.5", "zod": "^4.6.5" } }, "sha512-ZM9aZEtK/3dudeM2t5N9lujIpqEeDDOi6uHQp63mYOsBLlEUFEKff6y6WLicJJxGT9BM3h0mxh1Sv9YBT6jceQ=="],
+ "@moq/msf": ["@moq/msf@workspace:moq/js/msf"],
- "@moq/net": ["@moq/net@0.4.5", "", { "dependencies": { "@moq/pattern": "^0.2.1", "@moq/qmux": "^0.3.3", "@moq/signals": "^0.2.6", "async-mutex": "^0.5.0", "bowser": "^2.14.1" }, "peerDependencies": { "@zod/mini": "^4.5.0", "zod": "^4.5.0" } }, "sha512-Wk2KbfJIbtXEWmlRe48rNSvH71gIValtn5BC3814gQ/DV2dD8JqU8aSc7kJp9abxOjio6n8DJ3oDVual+ykCiw=="],
+ "@moq/net": ["@moq/net@workspace:moq/js/net"],
- "@moq/pattern": ["@moq/pattern@0.2.1", "", {}, "sha512-NXlegs0abkYoIUkcxyJjnUHC1CGw8VkooAbuNKLK5AF0hCAyUWw1QNliHaLBty4K76qfrrvJK6cf7j31Z0KF0A=="],
+ "@moq/pattern": ["@moq/pattern@workspace:moq/js/pattern"],
- "@moq/publish": ["@moq/publish@0.5.3", "", { "dependencies": { "@moq/hang": "^0.5.3", "@moq/json": "^0.4.2", "@moq/net": "^0.4.4", "@moq/signals": "^0.2.6", "mediabunny": "^1.58.1" } }, "sha512-lcpkq8izTK+e9rYvvtnFXkDiksnAUzhncZ5PzuP1N8vjHZVpV4E9AMHjnL100BFD8fGJox4sTSnjF8flh3IMrw=="],
+ "@moq/publish": ["@moq/publish@workspace:moq/js/publish"],
- "@moq/qmux": ["@moq/qmux@0.3.3", "", { "dependencies": { "@moq/web-socket-stream": "^0.1.1" } }, "sha512-Ez1GCVwpyNGB0eWvhKIXOjXrfRlMdmO0lpJ7gqInZFzsOsYTyML0WRgh24Itczp9Kev0CXsUA/K+XrGs1dRKUA=="],
+ "@moq/qmux": ["@moq/qmux@workspace:moq/js/qmux"],
- "@moq/signals": ["@moq/signals@0.2.6", "", { "peerDependencies": { "@types/react": "^19.2.17", "react": "^19.3.0", "solid-js": "^1.9.15" }, "optionalPeers": ["@types/react", "react", "solid-js"] }, "sha512-8L5y9cJSOVYGrHCZ20kcZsrmoxVXzl2dBQhsRj8H4zbbul70SmZcdeLG+O3JBP5W3tjo8EmLR+DOmh1/p7QQYw=="],
+ "@moq/signals": ["@moq/signals@workspace:moq/js/signals"],
- "@moq/watch": ["@moq/watch@0.6.6", "", { "dependencies": { "@moq/hang": "^0.5.4", "@moq/json": "^0.4.3", "@moq/msf": "^0.3.1", "@moq/net": "^0.4.5", "@moq/signals": "^0.2.6", "media-captions": "0.0.18" } }, "sha512-wtUoblpIS/UTgrW/IeQ6dlsZJDVo5nAbzU8oJWDU4K6JSiKyD1b08p19LPloeRJ/UPpA17r1PM4l35IOuoMXdg=="],
+ "@moq/video": ["@moq/video@workspace:moq/js/video"],
- "@moq/web-socket-stream": ["@moq/web-socket-stream@0.1.1", "", {}, "sha512-VKi/GE/CLqmbEZlgNbBLbAFk2pQTxT+KUD2DjCSoxqZxbS3KBe5BBZ0m3fila4fEGyll2JT5MmgEyVr4zkZEVA=="],
+ "@moq/watch": ["@moq/watch@workspace:moq/js/watch"],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.5", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "sha512-HshSvXzmBxNzqejd3k/KemgQ3hdREUYFRRrgxOZ+gPljDIsO2SDZrVZMd4Z/HsxudQbE62OVyVFE8z9BCurfjA=="],
@@ -383,10 +640,6 @@
"@speed-highlight/core": ["@speed-highlight/core@1.2.12", "", {}, "sha512-uilwrK0Ygyri5dToHYdZSjcvpS2ZwX0w5aSt3GCEN9hrjxWCoeV4Z2DTXuxjwbntaLQIEEAlCeNQss5SoHvAEA=="],
- "@svta/cml-iso-bmff": ["@svta/cml-iso-bmff@1.0.7", "", { "peerDependencies": { "@svta/cml-utils": "^1.6.2" } }, "sha512-pYM60+pcGd8G8USeVKOe4X25FdmKd4ihTLQWRszxSE/E0I/SFugqeexdlXyBiJbOhkzc8PFceIWv+RvrB9+NDw=="],
-
- "@svta/cml-utils": ["@svta/cml-utils@1.6.2", "", {}, "sha512-sJ4C70TSf7LmpaBGKK4NpbWWDiQE5ojqVjxsCZ1yDf6x6oj4bObuxC8525+SJAe4EHUfueVAR1I+kDavZmIAzg=="],
-
"@tailwindcss/forms": ["@tailwindcss/forms@0.5.11", "", { "dependencies": { "mini-svg-data-uri": "^1.2.3" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" } }, "sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA=="],
"@tailwindcss/node": ["@tailwindcss/node@4.3.3", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.3" } }, "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg=="],
@@ -423,6 +676,8 @@
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.4", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A=="],
+ "@types/audioworklet": ["@types/audioworklet@0.0.100", "", {}, "sha512-mWt4Z5CWi0CupDnSYWJgx6dw61XUk9JSRGrM9F/YNrpOoY/0XG+sxP+8rAjQq80H4febOe8PrzGmmN/X3w8LsQ=="],
+
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
@@ -449,8 +704,12 @@
"@types/node": ["@types/node@26.6.5", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg=="],
+ "@types/react": ["@types/react@19.3.0", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg=="],
+
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
+ "@typescript/lib-dom": ["@types/web@0.0.358", "", {}, "sha512-mNaTgu3PxD0NY0QTpMSFcs+BurApADI/h0oK/sKzkzobGw9F/GpAM4x3Vreul/EAsgm5I8uKzMnuSazZ1qHjgw=="],
+
"@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="],
"@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="],
@@ -495,7 +754,7 @@
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
- "@zod/mini": ["@zod/mini@4.6.5", "", { "peerDependencies": { "zod": "^4.6.0" } }, "sha512-TSoVYZYRDIVdCGpaikfJ+mZ6RDSb4fOT/iV/GGBpuaD4/+U9Y+gEVNVFJx0uYWbvQidIKNgPWKG/jy0o2VBoJg=="],
+ "@zod/mini": ["@zod/mini@4.6.7", "", { "peerDependencies": { "zod": "^4.6.0" } }, "sha512-oLORzy4OERpr+gaITCsrCK9uDWd9lOCLG00fRZEJidVITLS5SQtifmzP6fBSFH9YxkXFoT6/o4bRZDq2tTHrog=="],
"am-i-vibing": ["am-i-vibing@0.5.0", "", { "dependencies": { "process-ancestry": "^0.1.0" }, "bin": { "am-i-vibing": "dist/cli.mjs" } }, "sha512-KLcsDhqf1XRrJmJr+9I/C0EX2K+sGrAGZFgkmd84c8TJO9zJBu+/kFInjazu1KfJWFhWDKdRWkpBzEs7s35DVA=="],
@@ -515,6 +774,8 @@
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
+ "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
+
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.5", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-D5vIoztZOq1XM54LUdttJVc96ggEsIfju2JBvht06pSzpckp3C7HReun67Bghzrtdsq9XdMGbSSB3v3GhMNmAA=="],
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
@@ -523,7 +784,7 @@
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
- "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="],
+ "brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
@@ -615,6 +876,8 @@
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
+ "fast-check": ["fast-check@4.10.2", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-iK2f+YrcmoeGqk6fA0ea2bptcu/itMIm4NfEozq6N25+aG6h7s5HZbB/k1aV7b5w5sFLMCbbtRUsTVR+BgC3xw=="],
+
"fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="],
"fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="],
@@ -627,6 +890,8 @@
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
+ "fflate": ["fflate@0.8.3", "", {}, "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA=="],
+
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"flattie": ["flattie@1.1.1", "", {}, "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ=="],
@@ -645,6 +910,8 @@
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
+ "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
+
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
@@ -729,7 +996,7 @@
"media-captions": ["media-captions@0.0.18", "", {}, "sha512-JW18P6FuHdyLSGwC4TQ0kF3WdNj/+wMw2cKOb8BnmY6vSJGtnwJ+vkYj+IjHOV34j3XMc70HDeB/QYKR7E7fuQ=="],
- "mediabunny": ["mediabunny@1.61.3", "", { "dependencies": { "@types/dom-mediacapture-transform": "^0.1.11", "@types/dom-webcodecs": "0.1.13" } }, "sha512-kUqLKVXQVwHR62GkOJxYosAmXERJbqnXBgJDTtRlxHEYviVkMg/Evhl9XN6kedqS5LP1tV4kFKyzY/V4a4ofow=="],
+ "mediabunny": ["mediabunny@1.62.1", "", { "dependencies": { "@types/dom-mediacapture-transform": "^0.1.11", "@types/dom-webcodecs": "0.1.13" } }, "sha512-lYzeerDwo9QLhBCipqQDXOSV7InvaIFplllZQviPO5rPwH+IUr8M1uvumk1olIUgMRGkPiInRriv25WFErUgMw=="],
"merge-anything": ["merge-anything@5.1.7", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ=="],
@@ -747,6 +1014,10 @@
"miniflare": ["miniflare@5.20261012.1-beta", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "0.35.5", "undici": "7.29.1", "workerd": "1.20261012.1", "ws": "8.21.0", "youch": "4.1.0-beta.10" } }, "sha512-8Canhy6g9+vAhqMc0o80YphqYIwpiA3aAp8aX+uDXU7J/qbUeoZzcII9c2V1P4uT/eHD4SB93qet1o1bRY3BuQ=="],
+ "minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="],
+
+ "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
+
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
@@ -783,14 +1054,18 @@
"p-timeout": ["p-timeout@7.0.2", "", {}, "sha512-prbX4Z3YszrFNgH+MW5Zoeq3baXrMtP/MQnFeET90UB/GtGcGDQ5Usg9OCy6ETjTTntOw1SL2z9fMPUppN3Guw=="],
+ "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
+
"package-manager-detector": ["package-manager-detector@1.9.0", "", {}, "sha512-zAMfbta9nPA8AKkA4WdmNAT8R8s6Dk0/xwPsqNnRidMFcLo1YkLuPDTgs2f64XdzS8vbLNnoJ+uUIkgbz7oRHA=="],
- "pako": ["pako@3.0.1", "", {}, "sha512-GupotUUI0mlhugKjUs4bjOwLt3nrehy9Ys2dxC0GtgVef5cnKggkDMmf2bq2poCCuVXopWPmqsc9VDT2iJUy+w=="],
+ "pako": ["pako@3.0.2", "", {}, "sha512-uBv6IT2aT1A78iU6dpNEbf6+CyhlV/6g9JlJs9kpgjFGFhruIICVRysF/W0SLzXg5+hCl+KroH7e4YUyfEmgLg=="],
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
"path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="],
+ "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
+
"path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="],
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
@@ -811,8 +1086,12 @@
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
+ "pure-rand": ["pure-rand@8.4.2", "", {}, "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng=="],
+
"radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="],
+ "react": ["react@19.3.1", "", {}, "sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw=="],
+
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="],
@@ -825,6 +1104,8 @@
"retext-smartypants": ["retext-smartypants@6.2.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ=="],
+ "rimraf": ["rimraf@6.1.3", "", { "dependencies": { "glob": "^13.0.3", "package-json-from-dist": "^1.0.1" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA=="],
+
"rolldown": ["rolldown@1.2.14", "", { "dependencies": { "@oxc-project/types": "=0.154.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.14", "@rolldown/binding-android-arm64": "1.2.14", "@rolldown/binding-darwin-arm64": "1.2.14", "@rolldown/binding-darwin-x64": "1.2.14", "@rolldown/binding-freebsd-x64": "1.2.14", "@rolldown/binding-linux-arm-gnueabihf": "1.2.14", "@rolldown/binding-linux-arm64-gnu": "1.2.14", "@rolldown/binding-linux-arm64-musl": "1.2.14", "@rolldown/binding-linux-ppc64-gnu": "1.2.14", "@rolldown/binding-linux-s390x-gnu": "1.2.14", "@rolldown/binding-linux-x64-gnu": "1.2.14", "@rolldown/binding-linux-x64-musl": "1.2.14", "@rolldown/binding-openharmony-arm64": "1.2.14", "@rolldown/binding-win32-arm64-msvc": "1.2.14", "@rolldown/binding-win32-x64-msvc": "1.2.14" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw=="],
"satteri": ["satteri@0.10.5", "", { "dependencies": { "@types/estree-jsx": "^1.0.5", "@types/hast": "^3.0.5", "@types/mdast": "^4.0.4", "@types/unist": "^3.0.3" }, "optionalDependencies": { "@bruits/satteri-darwin-arm64": "0.10.5", "@bruits/satteri-darwin-x64": "0.10.5", "@bruits/satteri-linux-arm64-gnu": "0.10.5", "@bruits/satteri-linux-arm64-musl": "0.10.5", "@bruits/satteri-linux-x64-gnu": "0.10.5", "@bruits/satteri-linux-x64-musl": "0.10.5", "@bruits/satteri-wasm32-wasi": "0.10.5", "@bruits/satteri-win32-arm64-msvc": "0.10.5", "@bruits/satteri-win32-x64-msvc": "0.10.5" } }, "sha512-Ao1LKpAEa9Wdg0otgbVKViZHEq9ebdXe4DMrp3s9vQAU0HNIuHnFEuMuOcm0ZIXyV0Yzxj91NvhLpvXZJO/5ZQ=="],
@@ -919,6 +1200,8 @@
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
+ "uuid": ["uuid@14.0.3", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-Sc5u1VVln93y1Xnz4R+CunOZc7R9LUcTmhj7Rc7Eg3HVcf7rVn2FOoa812fe0W8p3jD7xeoGSYbfqCGGH2B6xw=="],
+
"verkit": ["verkit@0.5.0", "", {}, "sha512-u4J1hr6Y+C/hit34GwL3eIcUf3wngT8TlYUjK7tk0jxydKLAq3sYvifjdTDA6U3KCKRcQCQDGf/srjxfZF3l7g=="],
"vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
@@ -953,7 +1236,7 @@
"youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="],
- "zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
+ "zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
@@ -961,6 +1244,8 @@
"@astrojs/compiler-binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="],
+ "@astrojs/rss/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
+
"@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@babel/core/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
@@ -1029,8 +1314,6 @@
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
- "astro/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
-
"babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="],
"babel-plugin-jsx-dom-expressions/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
diff --git a/bunfig.toml b/bunfig.toml
new file mode 100644
index 00000000..e7b3cead
--- /dev/null
+++ b/bunfig.toml
@@ -0,0 +1,3 @@
+[test]
+# The moq submodule runs its own tests.
+pathIgnorePatterns = ["moq/**"]
diff --git a/env.d.ts b/env.d.ts
index a19b369a..c94105d5 100644
--- a/env.d.ts
+++ b/env.d.ts
@@ -1,4 +1,5 @@
///
+///
interface ImportMetaEnv {
readonly PUBLIC_RELAY_URL: string;
diff --git a/flake.nix b/flake.nix
index 4819eef4..346d4f41 100644
--- a/flake.nix
+++ b/flake.nix
@@ -41,17 +41,14 @@
assert pkgs.lib.assertMsg
((builtins.fromJSON (builtins.readFile ./package.json)).packageManager == "bun@${pkgs.bun.version}")
"Update package.json to match Nix Bun ${pkgs.bun.version}";
- assert pkgs.lib.assertMsg
- (pkgs.lib.hasPrefix "FROM oven/bun:${pkgs.bun.version}-slim AS base\n" (builtins.readFile ./Dockerfile))
- "Update Dockerfile to match Nix Bun ${pkgs.bun.version}";
pkgs.mkShell (
{
packages = with pkgs; [
# Everything in the justfile runs through bun: astro, vite, biome,
# tsc, and wrangler are all `bun run` or `bunx`.
#
- # Keep package.json and Dockerfile aligned when nixpkgs changes
- # Bun. CI reads package.json and evaluates the assertions above.
+ # Keep package.json aligned when nixpkgs changes Bun. CI reads
+ # package.json and evaluates the assertion above.
bun
# Astro and Vite target node, and parts of their toolchains shell
diff --git a/moq b/moq
new file mode 160000
index 00000000..89e1ff39
--- /dev/null
+++ b/moq
@@ -0,0 +1 @@
+Subproject commit 89e1ff39d35eaf41f3395c99b03721cd22dfe7e3
diff --git a/package.json b/package.json
index d5586377..5f94d341 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,23 @@
"type": "module",
"version": "0.0.1",
"private": true,
+ "workspaces": [
+ "moq/js/clock",
+ "moq/js/flate",
+ "moq/js/hang",
+ "moq/js/json",
+ "moq/js/libav",
+ "moq/js/loc",
+ "moq/js/moq-boy",
+ "moq/js/msf",
+ "moq/js/net",
+ "moq/js/pattern",
+ "moq/js/publish",
+ "moq/js/qmux",
+ "moq/js/signals",
+ "moq/js/video",
+ "moq/js/watch"
+ ],
"scripts": {
"dev": "astro dev --open",
"dev:pub": "vite sites/pub",
@@ -12,13 +29,14 @@
"deploy": "wrangler deploy",
"prod": "astro build --production && wrangler pages dev ./dist --open",
"check": "biome ci && tsc --noEmit",
- "fix": "biome check --write && bun audit fix"
+ "fix": "biome check --write && bun audit fix",
+ "postinstall": "bun run --cwd moq/js/libav build"
},
"dependencies": {
- "@moq/boy": "^0.4.1",
- "@moq/json": "^0.4.3",
- "@moq/publish": "^0.5.3",
- "@moq/watch": "^0.6.6",
+ "@moq/boy": "workspace:*",
+ "@moq/json": "workspace:*",
+ "@moq/publish": "workspace:*",
+ "@moq/watch": "workspace:*",
"astro": "^7.4.0",
"highlight.js": "^11.12.0",
"solid-js": "^1.9.17"
@@ -33,6 +51,7 @@
"@tailwindcss/vite": "^4.3.3",
"@types/bun": "^1.4.3",
"@types/node": "^26.6.5",
+ "rolldown": "^1.2.14",
"shiki": "^4.5.0",
"tailwindcss": "^4.3.3",
"typescript": "^7.0.2",
diff --git a/quest/README.md b/quest/README.md
index 2ae05c4f..f57d0018 100644
--- a/quest/README.md
+++ b/quest/README.md
@@ -10,10 +10,11 @@ versioned quests, grouped into milestones ordered by priority.
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
+only as a `moq` submodule 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) - hosted worklets once a release ships assets(), lean agent context, and checks that pass locally
+- [m0: immediate priorities](/quest/m0/README.md) - hosted worklets, lean agent context, and checks that pass locally
- [m1: next](/quest/m1/README.md) - the Voice AI demo page and its live call timeline
+- [m2: later](/quest/m2/README.md) - blog polish behind the demos
diff --git a/quest/m0/README.md b/quest/m0/README.md
index 41ae743d..f55a15df 100644
--- a/quest/m0/README.md
+++ b/quest/m0/README.md
@@ -2,8 +2,8 @@
## Goal
-The sites host the `@moq` player worklets and workers themselves once a
-release ships `assets()`, so the hosted path gets real traffic.
+The sites host the `@moq` player worklets and workers themselves, so the
+hosted path gets real traffic.
The agent context is lean and the checks cover what they claim, passing on a
developer's machine.
@@ -13,5 +13,4 @@ developer's machine.
- [Lean AGENTS.md](/quest/m0/lean-agents-md.md) - agent context keeps only rules and conventions, no repo description
- [Biome passes locally](/quest/m0/biome-local.md) - `just check` passes with Claude Code's local files present
- [Type-check .astro files](/quest/m0/astro-typecheck.md) - `just check` covers `.astro` via the TypeScript 7.1 content mapper
-- [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
deleted file mode 100644
index a51752d4..00000000
--- a/quest/m0/assets-release.md
+++ /dev/null
@@ -1,17 +0,0 @@
-# [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/dogfood-assets.md b/quest/m0/dogfood-assets.md
index 25660db4..6798a56b 100644
--- a/quest/m0/dogfood-assets.md
+++ b/quest/m0/dogfood-assets.md
@@ -14,15 +14,13 @@ 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
+- The `@moq` packages build from the `moq` submodule, and its `worklet()`
+ Vite plugin already emits each worklet and worker to `assets/.js` in
+ every site's build. Point `assets()` there rather than copying files, and
+ check that the dev server serves them too. `vite-plugin-static-copy` is
+ then an unused devDependency to drop.
+- Call `assets()` 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
diff --git a/quest/m0/lean-agents-md.md b/quest/m0/lean-agents-md.md
index 02047c8c..20d8695e 100644
--- a/quest/m0/lean-agents-md.md
+++ b/quest/m0/lean-agents-md.md
@@ -15,6 +15,5 @@ like the Bun version stop drifting because they are no longer written down.
code (`sites/*/wrangler.jsonc`, `sites/lib/worker.ts`). If something
load-bearing turns out to be missing, add it as a comment beside that code.
- The Bun pin-sync rule goes too. Make the flake's Bun assertion failure name
- all three pins (nixpkgs, `packageManager` in `package.json`, the `Dockerfile`
- base image), so the guidance appears exactly when CI catches the drift.
+ both pins (nixpkgs and `packageManager` in `package.json`), so the guidance appears exactly when CI catches the drift.
- Read `PROMPTING.md` first. `CLAUDE.md` is a symlink to `AGENTS.md`.
diff --git a/quest/m1/README.md b/quest/m1/README.md
index 9835e534..3e34c40d 100644
--- a/quest/m1/README.md
+++ b/quest/m1/README.md
@@ -7,6 +7,6 @@ moves and buffers the call and why it beats WebRTC for voice agents.
## Required
-- [An @moq/watch release ships per-frame arrivals](/quest/m1/watch-arrivals-release.md) - condition: the reply-side data is on npm
+- [Drain the voice goodbye instead of waiting](/quest/m1/voice-goodbye-drain.md) - hanging up closes through the draining `close()`, not a fixed wait
- [The voice agent publishes the full voice.json](/quest/m1/voice-timeline-track.md) - condition: the agent's side of the call, timed, on wtf
- [A live timeline of the voice call](/quest/m1/voice-buffers.md) - every frame, buffer, VAD decision, and model step on one axis, marking frames late for a typical WebRTC jitter buffer
diff --git a/quest/m1/voice-buffers.md b/quest/m1/voice-buffers.md
index 583a56f7..5bbc6048 100644
--- a/quest/m1/voice-buffers.md
+++ b/quest/m1/voice-buffers.md
@@ -2,8 +2,8 @@
## Goal
-The `/voice` demo draws the whole call on one live, scrolling timeline, from
-real measurements: your mic frames as captured, those frames reaching the
+The `/voice` demo draws the latest exchange, your most recent statement and
+the agent's most recent reply, on one live timeline from real measurements: your mic frames as captured, those frames reaching the
agent, its VAD and turn decisions, the words STT recognizes and when, the LLM
and TTS working, each reply frame published, in flight, and buffered in your
browser, and the playhead. A reader watches packets cross before the AI
@@ -21,8 +21,12 @@ Decided 2026-10-23:
- **One shared wall-clock axis**, not per-direction panels or strips under
words: every lane on the same time, so a frame's capture, arrival, and
- recognition line up vertically. A sliding window around now; buffered reply
- audio sits to the right of the playhead.
+ recognition line up vertically. Buffered reply audio sits to the right of
+ the playhead.
+- **The latest exchange, not the whole call** (decided later on 2026-10-23,
+ replacing a sliding window around now): the axis spans your most recent
+ statement and the agent's most recent reply. `voice.json` holds exactly
+ that, so the page draws what it reads.
- **All at once.** Build the page after the agent-side and reply-side data
exist, rather than shipping lanes piecemeal.
- **Real data only**, and keep the WebRTC mark: a frame is late for WebRTC
@@ -44,8 +48,11 @@ Sources, as of planning:
- Reply-side arrivals: `@moq/watch`'s per-frame `arrivals()`, which also
yields a gap item where media will never arrive. Draw a frame as lost only
from such a gap; until then a missing frame is in flight.
-- Agent side: `voice.json` on the response broadcast, extended to carry
- everything that happens on the agent with its time.
+- Agent side: `voice.json` on the response broadcast, a moq-json Window
+ carrying everything that happens on the agent with its time. At each
+ utterance boundary the agent cuts a group and pops everything older than
+ the latest statement on each side, so a joiner or reconnect gets exactly the
+ current exchange. Read it with `@moq/json`'s Window consumer.
The browser cannot see its own QUIC send buffer. Show "captured, not yet at
the agent" from the agent's arrivals instead.
@@ -61,5 +68,4 @@ Build on [moq-dev/moq.dev#194](https://github.com/moq-dev/moq.dev/pull/194)
## Required
-- [An @moq/watch release ships per-frame arrivals](/quest/m1/watch-arrivals-release.md) - the reply-side data
- [The voice agent publishes the full voice.json](/quest/m1/voice-timeline-track.md) - the agent's side of the timeline
diff --git a/quest/m1/voice-goodbye-drain.md b/quest/m1/voice-goodbye-drain.md
new file mode 100644
index 00000000..77c0b239
--- /dev/null
+++ b/quest/m1/voice-goodbye-drain.md
@@ -0,0 +1,19 @@
+# [XS] Drain the voice goodbye instead of waiting
+
+## Goal
+
+When a `/voice` call ends, the page's final `session-ending` record reaches
+the agent because the connection drains on close, not because the page waits
+a fixed 250 ms (`GOODBYE_MS` in `src/components/voice-demo.tsx`) before
+unmounting ``.
+
+## Plan
+
+Decided 2026-10-19: once `@moq/net`'s draining `close()` is available, drop
+the wait and close through it. The `moq` submodule now provides it.
+
+Check how `` closes its connection before relying on it: the
+element holds a pooled connection handle, and the pool lingers before
+closing, so unmounting may not drain the broadcast's last frames the way an
+explicit `close()` does. Prove it with a real call: hang up and confirm the
+agent logs the `session-ending` record rather than a timeout.
diff --git a/quest/m1/voice-timeline-track.md b/quest/m1/voice-timeline-track.md
index 34262e26..74d18b4a 100644
--- a/quest/m1/voice-timeline-track.md
+++ b/quest/m1/voice-timeline-track.md
@@ -3,9 +3,10 @@
## Goal
The moq.pro demo agent on wtf publishes a `voice.json` track on each session's
-response broadcast carrying the agent's whole side of the call, each with its
-time: request frame arrivals, VAD and turn decisions, word spans and when STT
-recognized them, LLM and TTS activity, and each reply frame as published.
+response broadcast carrying the agent's side of the latest exchange, each with
+its time: request frame arrivals, VAD and turn decisions, word spans and when
+STT recognized them, LLM and TTS activity, and each reply frame as published.
+It is a moq-json Window holding the latest statement on each side.
This quest tracks a condition outside the repository. When it holds, delete
this quest and every `Required` entry that links it.
@@ -18,7 +19,9 @@ moq.pro owns the schema and the Pipecat fork hooks behind it:
version 1 (arrivals and word spans) merged in
[moq-dev/moq.pro#2319](https://github.com/moq-dev/moq.pro/pull/2319) and
awaits its wtf proof
-- [voice.json carries the agent's whole pipeline](https://github.com/moq-dev/moq.pro/blob/main/quest/m2/voice-timeline-pipeline.md) -
- the rest
+- The rest is [moq-dev/moq.pro#2877](https://github.com/moq-dev/moq.pro/pull/2877).
+ Its Window needs moq-json's Window in moq's FFI bindings first, since a
+ Stream stops at 8,192 records, about 164 s into a call.
-Check by subscribing to the track during a staging session.
+Check by subscribing to the track during a staging session the maintainer
+calls.
diff --git a/quest/m1/watch-arrivals-release.md b/quest/m1/watch-arrivals-release.md
deleted file mode 100644
index 8470a706..00000000
--- a/quest/m1/watch-arrivals-release.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# [XS] An @moq/watch release ships per-frame arrivals
-
-## Goal
-
-The newest `@moq/watch` on npm exposes the per-frame `arrivals()` handle on
-the audio and video decoders, and moq.dev builds against it.
-
-This quest tracks a condition outside the repository. When it holds, bump
-`@moq/watch` here, then delete this quest and every `Required` entry that
-links it.
-
-## Plan
-
-moq shipped the handle in
-[moq-dev/moq#5311](https://github.com/moq-dev/moq/pull/5311) on `main`, a
-breaking change. 0.6.3 came from moq's `release` branch without it.
-Check `npm view @moq/watch version` and its type definitions for
-`Audio.Decoder.arrivals()` and `Watch.Arrival`.
-
-Decided 2026-10-19: the same bump brings `@moq/net`'s draining `close()`
-(also on moq `main` only). With it, drop the voice page's 250 ms wait before
-closing (`GOODBYE_MS` in `src/components/voice-demo.tsx`) and close the
-connection with `close()` so the final `session-ending` record still reaches
-the agent.
diff --git a/quest/m2/README.md b/quest/m2/README.md
new file mode 100644
index 00000000..0afa493e
--- /dev/null
+++ b/quest/m2/README.md
@@ -0,0 +1,10 @@
+# m2: later
+
+## Goal
+
+Site work after the Voice AI demo: blog polish that improves every post but
+waits behind the demos.
+
+## Required
+
+- [Hand-drawn blog art as SVG](/quest/m2/site-svg.md) - the blog's hand-drawn PNGs become transparent palette SVGs, covers rasterized for social cards at build
diff --git a/quest/m2/site-svg.md b/quest/m2/site-svg.md
new file mode 100644
index 00000000..10310409
--- /dev/null
+++ b/quest/m2/site-svg.md
@@ -0,0 +1,66 @@
+# [M] Hand-drawn blog art as SVG
+
+## Goal
+
+The hand-drawn diagrams and sketches in the moq.dev blog are served as SVG instead of PNG, on a transparent background in the site
+palette, so each sits on the page itself at any resolution. Social cards
+still get a PNG. Screenshots, memes, photos, charts, and logos stay raster.
+A just recipe turns a newly drawn PNG into the same kind of SVG.
+
+## Plan
+
+Decided 2026-10-20 in planning:
+
+- **Hand-drawn only.** As of 2026-10-20 moq.dev holds 55 hand-drawn PNGs
+ across the blog posts plus the author avatar. They are white (`#FAFAFA`),
+ green (`#00BE2D`), and grey (`#A3A5A8`) strokes, some with yellow, red, or
+ orange accents. Vectorizing the screenshots, memes, photos, and charts gains
+ nothing.
+- **Transparent background, palette colours, no light mode.** The site is
+ dark-only (`color-scheme: dark` on slate-900, no `prefers-color-scheme`) and
+ embeds images as markdown `
`, which CSS variables cannot reach. Drop
+ the baked background, most often `#0F172A`, but `#292929` and `#212121` in
+ quic-powers, which show as grey boxes today. Snap the antialiased stroke
+ colours to the palette. `currentColor` was rejected because it only works
+ for inlined SVG and the site has no light theme to serve.
+- **Per-colour potrace, keeping the hand-drawn look.** Quantize to the
+ palette, then blur, threshold, and potrace each colour layer, the pipeline
+ the site's existing drawn SVGs already come from. vtracer was rejected for
+ noisier stroke output that does not match them. Redrawing by hand was
+ rejected as 55 images of manual work that changes the look. No editable
+ sources exist anywhere in moq.dev's history, only the flattened PNGs.
+- **Mixed images stay raster:** `to-wasm/slide.png` (pasted Ferris and
+ TypeScript bitmaps), `on-a-boat/kraken.png` (a photo grid), and
+ `update-00/globe.png` (a screenshot). Filled drawings such as
+ `quic-powers/vegeta.png`, `kixelCat.png`, `be-the-outlier/narwhal.png`,
+ `on-a-boat/boat.png`, `first-cdn/cloudflare.png`, and `moqbs/moqbs.png` are
+ traced too. Any that comes out visibly worse or larger than its PNG stays
+ PNG, and the PR lists each one. Embedding bitmaps inside an SVG was
+ rejected because it defeats the point.
+- **Covers are rasterized at build time.** About a third of the drawings are
+ a post's frontmatter `cover:`, which becomes `og:image`. Social crawlers
+ cannot render SVG, so `scripts/check-og.ts` rejects it. The Astro build
+ renders each SVG cover to PNG on the slate background for `og:image`, so the
+ SVG is the only committed source and nothing can drift. Committing a PNG
+ copy per cover, and leaving covers raster, were both rejected.
+- **One tool.** Generalize `tools/icons/smooth.sh`, which renders,
+ blurs, thresholds, and potraces the homepage icons
+ ([moq-dev/moq.dev#163](https://github.com/moq-dev/moq.dev/pull/163)), so it
+ traces drawn PNGs as well. Do not keep two near-duplicate pipelines. Expose
+ it as a just recipe, with potrace in the Nix dev shell. Hand-drawn posts
+ keep coming.
+- **Docs: one line in `AGENTS.md`** on the drawn-PNG recipe. A doc page
+ was rejected because readers of the docs site never see this.
+- **Lands here.** Planned in moq-dev/moq to follow moq.dev's move into that
+ repository; moved here 2026-10-23 when the move was dropped, since moq.dev
+ builds from a `moq` submodule instead.
+
+Guidance:
+
+- Delete each replaced PNG and update every reference, including posts that
+ reuse another post's image (`robotics.mdx` reuses five), the cover, and
+ `components/author.astro` for the avatar.
+- The drawings were made at fixed sizes (960x540, 1920x1080, 1920x640). Keep
+ the `viewBox` at the original size so layout does not shift.
+- Compare each SVG against its PNG in the dev server at 1x and 2x before
+ calling it done. Check a cover's built `og:image` too.
diff --git a/sites/pub/vite.config.ts b/sites/pub/vite.config.ts
index e4c6c839..2ad419bc 100644
--- a/sites/pub/vite.config.ts
+++ b/sites/pub/vite.config.ts
@@ -1,4 +1,5 @@
import { defineConfig } from "vite";
+import { worklet } from "../../moq/js/common/vite-plugin-worklet.ts";
import { routing } from "../lib/dev";
// moq.pub — a static Vite build of the @moq/publish player. The Worker in
@@ -8,7 +9,7 @@ export default defineConfig({
// PUBLIC_ prefix rather than Vite's default VITE_.
envDir: "../..",
envPrefix: "PUBLIC_",
- plugins: [routing()],
+ plugins: [routing(), worklet()],
build: {
target: "esnext",
outDir: "dist",
diff --git a/sites/watch/vite.config.ts b/sites/watch/vite.config.ts
index c81eb42a..4e7c02a9 100644
--- a/sites/watch/vite.config.ts
+++ b/sites/watch/vite.config.ts
@@ -1,4 +1,5 @@
import { defineConfig } from "vite";
+import { worklet } from "../../moq/js/common/vite-plugin-worklet.ts";
import { routing } from "../lib/dev";
// moq.watch — a static Vite build of the @moq/watch player. The Worker in
@@ -8,7 +9,7 @@ export default defineConfig({
// PUBLIC_ prefix rather than Vite's default VITE_.
envDir: "../..",
envPrefix: "PUBLIC_",
- plugins: [routing()],
+ plugins: [routing(), worklet()],
build: {
target: "esnext",
outDir: "dist",
diff --git a/src/components/voice-demo.tsx b/src/components/voice-demo.tsx
index a7d9956a..61aaf8fa 100644
--- a/src/components/voice-demo.tsx
+++ b/src/components/voice-demo.tsx
@@ -3,7 +3,7 @@
// Pipecat client: sends the mic, plays the reply, and the page speaks
// the slice of RTVI the agent needs over a `transcript.json.z` side track in each direction.
import * as Json from "@moq/json";
-import { Net, Signals } from "@moq/publish";
+import { Clock, Net, Signals } from "@moq/publish";
import "@moq/publish/element";
import type MoqPublish from "@moq/publish/element";
import "@moq/watch/element";
@@ -223,7 +223,7 @@ export default function VoiceDemo() {
const send = (call: Call, message: Message) => {
const record = { ...message, seq: call.log.length, epoch: call.epoch };
call.log.push(record);
- call.producer?.append(record);
+ call.producer?.append({ value: record });
};
// Tear the call down and show why. `undefined` is the reader's own Stop.
@@ -420,7 +420,7 @@ export default function VoiceDemo() {
track: broadcast.createTrack(RTVI_TRACK),
compression: "deflate",
});
- for (const record of call.log) producer.append(record);
+ for (const record of call.log) producer.append({ value: record });
call.producer = producer;
effect.cleanup(() => {
if (call.producer === producer) call.producer = undefined;
@@ -447,7 +447,9 @@ export default function VoiceDemo() {
effect.cleanup(() => track.close());
const consumer = new Json.Stream.Consumer({ track, compression: "deflate" });
effect.spawn(async () => {
- for await (const { seq, epoch, ...message } of consumer) {
+ for await (const {
+ value: { seq, epoch, ...message },
+ } of consumer) {
if (typeof seq === "number") {
if (epoch !== seen.epoch) Object.assign(seen, { epoch, seq: -1 });
if (seq <= seen.seq) continue;
@@ -576,9 +578,9 @@ export default function VoiceDemo() {
prop:url={url}
prop:name={Net.Path.from(call.session.responseBroadcast)}
prop:volume={1}
- prop:delay={Net.Time.Milli(100)}
+ prop:delay={Clock.Milli(100)}
// The agent sends its reply up to ~25s ahead of real time; holding less drops it.
- prop:buffer={Net.Time.Milli(30_000)}
+ prop:buffer={Clock.Milli(30_000)}
/>
>
);
diff --git a/src/components/watch.tsx b/src/components/watch.tsx
index fc120d5f..8e671cff 100644
--- a/src/components/watch.tsx
+++ b/src/components/watch.tsx
@@ -3,7 +3,7 @@ import * as Try from "@/lib/try";
import "@moq/watch/support/element";
import "@moq/watch/element";
import "@moq/watch/ui";
-import { Net } from "@moq/watch";
+import { Clock, Net } from "@moq/watch";
export default function Watch() {
const [session] = createResource(Try.watch);
@@ -31,7 +31,7 @@ export default function Watch() {
prop:name={Net.Path.from(current().broadcast.name)}
prop:muted={true}
// The default "auto" sizes the buffer from RTT alone, too small for relay jitter.
- prop:delay={Net.Time.Milli(100)}
+ prop:delay={Clock.Milli(100)}
>
diff --git a/tsconfig.json b/tsconfig.json
index 3f65e185..21439c77 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,6 @@
{
"extends": "astro/tsconfigs/strict",
- "exclude": ["**/dist", "node_modules"],
+ "exclude": ["**/dist", "node_modules", "moq"],
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "solid-js",