From 8c97fc1c547cfb4ec4799bc4c2e70af7831bee07 Mon Sep 17 00:00:00 2001 From: James Reetzke Date: Wed, 11 Feb 2026 22:10:02 -0500 Subject: [PATCH 01/17] refactor: extract shared UI into @moq/ui-core package (Milestone 1) Extract shared UI components (Button, Icon, Stats) and CSS (variables, flex, button styles, stats styles) from @moq/hang-ui into a new @moq/ui-core package. This is the first milestone in restructuring the codebase to separate watch/publish from @moq/hang. Files changed: - js/ui-core/ (new): New @moq/ui-core package with vite lib build, containing all shared UI components, icons (20 SVGs), stats panel with providers, and CSS theme variables. - js/hang-ui/src/shared/ (deleted): Moved entirely into @moq/ui-core. - js/hang-ui/src/watch/element.tsx: Stats import now from @moq/ui-core. - js/hang-ui/src/watch/components/{PlayPauseButton,FullscreenButton, VolumeSlider,StatsButton}.tsx: Button and Icon imports now from @moq/ui-core instead of relative ../../shared/ paths. - js/hang-ui/src/publish/components/{CameraSourceButton,FileSourceButton, ScreenSourceButton,MediaSourceSelector,NothingSourceButton, MicrophoneSourceButton}.tsx: Same import update as watch components. - js/hang-ui/src/watch/styles/index.css: CSS @imports updated from relative ../../shared/ paths to @moq/ui-core package paths. - js/hang-ui/src/publish/styles/index.css: Same CSS @import update. - js/hang-ui/package.json: Added @moq/ui-core as peer dependency. - js/hang-ui/vite.config.ts: Added @moq/ui-core to rollup externals so it is not bundled into the hang-ui build output. - js/hang-ui/README.md: Updated project structure and module overview to reflect that shared/ has moved to @moq/ui-core. - package.json (root): Added js/ui-core to workspaces array. - CLAUDE.md: Added ui-core/ to the JS project structure listing. - README.md (root): Added @moq/ui-core row to the TypeScript packages table. - js/docs/restructure-plan.md (new): Full restructuring plan with 8 milestones covering the migration from the current @moq/hang monolith to separate @moq/watch, @moq/publish, and @moq/ui-core packages. --- CLAUDE.md | 1 + README.md | 1 + bun.lock | 75 +++++ js/docs/restructure-plan.md | 300 ++++++++++++++++++ js/hang-ui/README.md | 33 +- js/hang-ui/package.json | 3 +- .../publish/components/CameraSourceButton.tsx | 3 +- .../publish/components/FileSourceButton.tsx | 3 +- .../components/MediaSourceSelector.tsx | 3 +- .../components/MicrophoneSourceButton.tsx | 3 +- .../components/NothingSourceButton.tsx | 3 +- .../publish/components/ScreenSourceButton.tsx | 3 +- js/hang-ui/src/publish/styles/index.css | 6 +- .../src/watch/components/FullscreenButton.tsx | 3 +- .../src/watch/components/PlayPauseButton.tsx | 3 +- .../src/watch/components/StatsButton.tsx | 3 +- .../src/watch/components/VolumeSlider.tsx | 3 +- js/hang-ui/src/watch/element.tsx | 2 +- js/hang-ui/src/watch/styles/index.css | 8 +- js/hang-ui/vite.config.ts | 2 +- js/ui-core/README.md | 38 +++ js/ui-core/package.json | 34 ++ .../src}/button/button.css | 0 .../src}/button/button.tsx | 0 .../src/shared => ui-core/src}/flex.css | 0 .../src}/icon/arrow-down.svg | 0 .../src}/icon/arrow-up.svg | 0 .../components => ui-core/src}/icon/audio.svg | 0 .../components => ui-core/src}/icon/ban.svg | 0 .../src}/icon/buffer.svg | 0 .../src}/icon/camera.svg | 0 .../components => ui-core/src}/icon/file.svg | 0 .../src}/icon/fullscreen-enter.svg | 0 .../src}/icon/fullscreen-exit.svg | 0 .../components => ui-core/src}/icon/icon.tsx | 0 .../src}/icon/microphone.svg | 0 .../components => ui-core/src}/icon/mute.svg | 0 .../src}/icon/network.svg | 0 .../components => ui-core/src}/icon/pause.svg | 0 .../components => ui-core/src}/icon/play.svg | 0 .../src}/icon/screen.svg | 0 .../components => ui-core/src}/icon/stats.svg | 0 .../components => ui-core/src}/icon/video.svg | 0 .../src}/icon/volume-high.svg | 0 .../src}/icon/volume-low.svg | 0 .../src}/icon/volume-medium.svg | 0 js/ui-core/src/index.ts | 4 + .../src}/stats/README.md | 0 .../src}/stats/components/StatsItem.tsx | 0 .../src}/stats/components/StatsPanel.tsx | 0 .../src}/stats/index.tsx | 0 .../src}/stats/providers/audio.ts | 0 .../src}/stats/providers/base.ts | 0 .../src}/stats/providers/buffer.ts | 0 .../src}/stats/providers/index.ts | 0 .../src}/stats/providers/network.ts | 0 .../src}/stats/providers/registry.ts | 0 .../src}/stats/providers/video.ts | 0 .../src}/stats/styles/index.css | 0 .../components => ui-core/src}/stats/types.ts | 0 .../src/shared => ui-core/src}/variables.css | 0 js/ui-core/src/vite-env.d.ts | 1 + js/ui-core/tsconfig.json | 11 + js/ui-core/vite.config.ts | 20 ++ package.json | 1 + 65 files changed, 520 insertions(+), 50 deletions(-) create mode 100644 js/docs/restructure-plan.md create mode 100644 js/ui-core/README.md create mode 100644 js/ui-core/package.json rename js/{hang-ui/src/shared/components => ui-core/src}/button/button.css (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/button/button.tsx (100%) rename js/{hang-ui/src/shared => ui-core/src}/flex.css (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/arrow-down.svg (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/arrow-up.svg (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/audio.svg (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/ban.svg (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/buffer.svg (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/camera.svg (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/file.svg (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/fullscreen-enter.svg (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/fullscreen-exit.svg (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/icon.tsx (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/microphone.svg (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/mute.svg (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/network.svg (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/pause.svg (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/play.svg (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/screen.svg (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/stats.svg (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/video.svg (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/volume-high.svg (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/volume-low.svg (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/icon/volume-medium.svg (100%) create mode 100644 js/ui-core/src/index.ts rename js/{hang-ui/src/shared/components => ui-core/src}/stats/README.md (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/stats/components/StatsItem.tsx (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/stats/components/StatsPanel.tsx (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/stats/index.tsx (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/stats/providers/audio.ts (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/stats/providers/base.ts (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/stats/providers/buffer.ts (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/stats/providers/index.ts (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/stats/providers/network.ts (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/stats/providers/registry.ts (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/stats/providers/video.ts (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/stats/styles/index.css (100%) rename js/{hang-ui/src/shared/components => ui-core/src}/stats/types.ts (100%) rename js/{hang-ui/src/shared => ui-core/src}/variables.css (100%) create mode 100644 js/ui-core/src/vite-env.d.ts create mode 100644 js/ui-core/tsconfig.json create mode 100644 js/ui-core/vite.config.ts diff --git a/CLAUDE.md b/CLAUDE.md index e5fa919ed..b1f9b2c94 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,6 +57,7 @@ Key architectural rule: The CDN/relay does not know anything about media. Anythi token/ # JWT token generation (published as @moq/token) clock/ # Clock example (published as @moq/clock) hang/ # Media layer (published as @moq/hang) + ui-core/ # Shared UI components (published as @moq/ui-core) hang-ui/ # Web Components UI (published as @moq/hang-ui) hang-demo/ # Demo applications diff --git a/README.md b/README.md index ab56f86a8..4a7078672 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ This repository provides both [Rust](/rs) and [TypeScript](/js) libraries with s | **[@moq/token](js/token)** | Authentication library & CLI for JS/TS environments (see [Authentication](doc/concept/authentication.md)) | [![npm](https://img.shields.io/npm/v/@moq/token)](https://www.npmjs.com/package/@moq/token) | | **[@moq/hang](js/hang)** | Media-specific encoding/streaming layered on top of `moq-lite`. Provides both a Javascript API and Web Components. | [![npm](https://img.shields.io/npm/v/@moq/hang)](https://www.npmjs.com/package/@moq/hang) | | **[@moq/hang-demo](js/hang-demo)** | Examples using `@moq/hang`. | | +| **[@moq/ui-core](js/ui-core)** | Shared UI components (Button, Icon, Stats, CSS theme) used by hang-ui. | [![npm](https://img.shields.io/npm/v/@moq/ui-core)](https://www.npmjs.com/package/@moq/ui-core) | | **[@moq/hang-ui](js/hang-ui)**. | UI Components that interact with the Hang Web Components using SolidJS. | [![npm](https://img.shields.io/npm/v/@moq/hang-ui)](https://www.npmjs.com/package/@moq/hang-ui) | diff --git a/bun.lock b/bun.lock index fc302a543..fb3f3280b 100644 --- a/bun.lock +++ b/bun.lock @@ -89,6 +89,7 @@ "peerDependencies": { "@moq/hang": "workspace:^0.1.0", "@moq/signals": "workspace:^0.1.0", + "@moq/ui-core": "workspace:^0.1.0", }, }, "js/lite": { @@ -152,6 +153,22 @@ "typescript": "^5.9.2", }, }, + "js/ui-core": { + "name": "@moq/ui-core", + "version": "0.1.0", + "devDependencies": { + "@typescript/lib-dom": "npm:@types/web@^0.0.241", + "rimraf": "^6.0.1", + "solid-js": "^1.9.10", + "typescript": "^5.9.2", + "vite": "^7.3.1", + "vite-plugin-solid": "^2.11.10", + }, + "peerDependencies": { + "@moq/hang": "workspace:^0.1.0", + "@moq/signals": "workspace:^0.1.0", + }, + }, }, "trustedDependencies": [ "@fails-components/webtransport-transport-http3-quiche", @@ -411,6 +428,8 @@ "@moq/token": ["@moq/token@workspace:js/token"], + "@moq/ui-core": ["@moq/ui-core@workspace:js/ui-core"], + "@moq/web-transport-ws": ["@moq/web-transport-ws@0.1.2", "", {}, "sha512-mYha+AkLNPT3uOGnTA5YWjpxc9LO/yriFSoWzKkR0zN3UMZb9RXbsD8Gbhg1pJZod6QD4tevHoOWTBADYN7yAQ=="], "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], @@ -1421,6 +1440,8 @@ "@moq/hang-ui/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], + "@moq/ui-core/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], + "@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], @@ -1501,6 +1522,8 @@ "@moq/hang-ui/vite/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], + "@moq/ui-core/vite/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], + "module-lookup-amd/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], "unplugin-solid/merge-anything/is-what": ["is-what@5.5.0", "", {}, "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw=="], @@ -1613,6 +1636,58 @@ "@moq/hang-ui/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], + "@moq/ui-core/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], + + "@moq/ui-core/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="], + + "@moq/ui-core/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.2", "", { "os": "android", "cpu": "arm64" }, "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA=="], + + "@moq/ui-core/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.2", "", { "os": "android", "cpu": "x64" }, "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A=="], + + "@moq/ui-core/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg=="], + + "@moq/ui-core/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA=="], + + "@moq/ui-core/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g=="], + + "@moq/ui-core/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA=="], + + "@moq/ui-core/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.2", "", { "os": "linux", "cpu": "arm" }, "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw=="], + + "@moq/ui-core/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw=="], + + "@moq/ui-core/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w=="], + + "@moq/ui-core/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg=="], + + "@moq/ui-core/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw=="], + + "@moq/ui-core/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ=="], + + "@moq/ui-core/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA=="], + + "@moq/ui-core/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w=="], + + "@moq/ui-core/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA=="], + + "@moq/ui-core/vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw=="], + + "@moq/ui-core/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.2", "", { "os": "none", "cpu": "x64" }, "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA=="], + + "@moq/ui-core/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA=="], + + "@moq/ui-core/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg=="], + + "@moq/ui-core/vite/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag=="], + + "@moq/ui-core/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg=="], + + "@moq/ui-core/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg=="], + + "@moq/ui-core/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ=="], + + "@moq/ui-core/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], + "module-lookup-amd/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "vitepress/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="], diff --git a/js/docs/restructure-plan.md b/js/docs/restructure-plan.md new file mode 100644 index 000000000..08ac87ffb --- /dev/null +++ b/js/docs/restructure-plan.md @@ -0,0 +1,300 @@ +# Restructuring Plan: `@moq/hang` → Separate Watch/Publish Packages + +## Background + +The goal is to split watch and publish functionality out of `@moq/hang` so that `@moq/hang` is strictly the core protocol + container logic. Watch and publish move into their own packages (`@moq/watch`, `@moq/publish`), each co-locating their UI web component (e.g. `@moq/watch/ui`). Shared UI primitives (button, icons, stats, CSS variables) go into `@moq/ui-core`. + +## Current Structure + +| Package | Contents | Build | +|---|---|---| +| `@moq/hang` | catalog, container, watch, publish, support, util | `tsc` | +| `@moq/hang-ui` | watch UI, publish UI, shared UI (button, icons, stats, CSS) | vite lib mode | +| `@moq/hang-demo` | demo app consuming both | vite | + +### Key dependencies + +- `hang/src/watch/` and `hang/src/publish/` both import `../catalog` (broadcast, preview, user) and depend on `@moq/lite` + `@moq/signals`. +- `hang-ui/src/watch/` and `hang-ui/src/publish/` both import from `../shared/` (button, icons, stats, CSS variables). +- `hang-ui` is built with vite library mode and uses SolidJS web components. +- AudioWorklets exist in `publish/audio/capture-worklet.ts`. + +## Target Structure + +| Package | Contents | Build | +|---|---|---| +| `@moq/hang` | catalog, container, support, util only | tsc (or vite) | +| `@moq/watch` | watch logic + `@moq/watch/ui` web component | vite lib mode | +| `@moq/publish` | publish logic + `@moq/publish/ui` web component | vite lib mode | +| `@moq/ui-core` | shared UI: button, icons, stats, CSS variables | vite lib mode | +| `@moq/hang-demo` | updated demo | vite | + +### Final dependency graph + +``` +@moq/lite ←─────────────────────────────┐ +@moq/signals ←───────────────────────┐ │ +@moq/hang (catalog, container) ←──┐ │ │ +@moq/ui-core (button, icons, etc) │ │ │ + │ │ │ +@moq/watch ───────────────────┴──┴────┤ + └─ /ui (imports @moq/ui-core) │ +@moq/publish ───────────────────┴──┴────┘ + └─ /ui (imports @moq/ui-core) +``` + +--- + +## Cross-Cutting Concerns + +These items span multiple milestones and should be addressed as each milestone is completed: + +### Root `package.json` workspaces +The root `package.json` `workspaces` array currently lists `js/hang`, `js/hang-ui`, and `js/hang-demo`. As new packages are created and old ones removed: +- **M1:** Add `js/ui-core` +- **M2:** Add `js/watch` +- **M3:** Add `js/publish` +- **M6:** Remove `js/hang-ui` + +### VitePress sidebar (`doc/.vitepress/config.ts`) +The sidebar currently nests Watch and Publish under `@moq/hang` and has a separate `@moq/hang-ui` entry. This needs restructuring: +- **M2–M3:** Move Watch/Publish to top-level sidebar entries (`@moq/watch`, `@moq/publish`) +- **M1:** Add `@moq/ui-core` sidebar entry +- **M6:** Remove `@moq/hang-ui` sidebar entry + +### `CLAUDE.md` +References `hang-ui/` in the project structure and architecture layers. Update: +- **M1:** Add `ui-core/` to the project structure +- **M2–M3:** Add `watch/` and `publish/` to the project structure +- **M6:** Remove `hang-ui/` from the project structure, update architecture description + +### Root `README.md` +Package table includes `@moq/hang-ui`. Update: +- **M1:** Add `@moq/ui-core` row +- **M2–M3:** Add `@moq/watch` and `@moq/publish` rows, update `@moq/hang` description +- **M6:** Remove `@moq/hang-ui` row + +### `doc/index.md` +References `@moq/hang-ui` in the highlights list. Update alongside M6. + +--- + +## Milestones + +### Milestone 1: Create `@moq/ui-core` package + +**Why first:** Prerequisite for Milestones 4 & 5. Extracting shared UI early establishes the `@moq/ui-core` contract before anything else moves. + +**Scope:** +- Create `js/ui-core/` with `package.json` (`@moq/ui-core`), `vite.config.ts` (library mode), `tsconfig.json`. +- Move `hang-ui/src/shared/` contents into `ui-core/src/` (button, icons + SVGs, stats, `variables.css`, `flex.css`). +- The icon component uses `?raw` SVG imports — vite handles this natively. +- Update `hang-ui/src/watch/` and `hang-ui/src/publish/` to import from `@moq/ui-core` instead of `../../shared/`. +- Verify `hang-ui` still builds and `hang-demo` still works. + +**README updates:** +- Create a new `js/ui-core/README.md` documenting the shared components (button, icon, stats), CSS variables, and usage. +- Move the stats component README (`hang-ui/src/shared/components/stats/README.md`) into `ui-core` alongside the component. +- Update `hang-ui/README.md` "shared/" section under "Project Structure" and "Module Overview" to note that shared components now come from `@moq/ui-core`. + +**Other updates:** +- Add `js/ui-core` to root `package.json` workspaces. +- Add `@moq/ui-core` entry to `doc/.vitepress/config.ts` sidebar. +- Add `ui-core/` to `CLAUDE.md` project structure. +- Add `@moq/ui-core` row to root `README.md` package table. + +**Exit criteria:** `@moq/ui-core` builds independently; `@moq/hang-ui` builds and works with `@moq/ui-core` as a dependency. + +--- + +### Milestone 2: Create `@moq/watch` package (logic only) + +**Scope:** +- Create `js/watch/` with `package.json`, `vite.config.ts`, `tsconfig.json`. +- Move `hang/src/watch/` → `watch/src/`. +- Change 3 internal imports (`../catalog` in `broadcast.ts`, `preview.ts`, `user.ts`) to `@moq/hang/catalog`. +- Dependencies: `@moq/hang` (for catalog), `@moq/lite`, `@moq/signals`, plus watch-specific deps from hang's `package.json` (e.g. `@kixelated/libavjs-webcodecs-polyfill`, `@libav.js/variant-opus-af`). +- Remove `./watch` and `./watch/element` exports from `hang/package.json`. +- Remove `export * as Watch from "./watch"` from `hang/src/index.ts`. + +**README updates:** +- Create a new `js/watch/README.md` documenting the `@moq/watch` package: installation, JS API, and web component usage (`` element and attributes). +- Pull the relevant content from `hang/README.md` sections: `` attributes, the watch portion of the JS API example, and tree-shaking notes. +- Update `hang/README.md`: + - Remove the `` section and its code examples. + - Remove watch-related JS API examples (`Hang.Watch.Broadcast`, etc.). + - Add a note pointing users to `@moq/watch` for watch functionality. + +**Doc site updates:** +- Move `doc/js/@moq/hang/watch.md` → `doc/js/@moq/watch.md` (or similar), update all import paths from `@moq/hang/watch` to `@moq/watch`. +- Update `doc/js/@moq/hang/index.md` to remove watch sections and link to the new `@moq/watch` page. +- Update `doc/js/index.md` "Quick Start" examples to use `@moq/watch` imports. +- Update `doc/js/env/web.md` `` section: change imports from `@moq/hang/watch/element` to `@moq/watch/element`. +- Update `doc/js/@moq/signals.md` example import. +- Update `doc/.vitepress/config.ts` sidebar: move Watch out from under `@moq/hang` to a top-level `@moq/watch` entry. + +**Other updates:** +- Add `js/watch` to root `package.json` workspaces. +- Add `watch/` to `CLAUDE.md` project structure. +- Add `@moq/watch` row to root `README.md` package table. + +**Exit criteria:** `@moq/watch` builds. `@moq/hang` builds without watch. + +> **Note:** Milestones 2 and 3 can be done in parallel. + +--- + +### Milestone 3: Create `@moq/publish` package (logic only) + +**Scope:** +- Create `js/publish/` with `package.json`, `vite.config.ts`, `tsconfig.json`. +- Move `hang/src/publish/` → `publish/src/`. +- Change 3 internal imports (`../catalog` in `broadcast.ts`, `preview.ts`, `user.ts`) to `@moq/hang/catalog`. +- **AudioWorklet handling:** `publish/audio/capture-worklet.ts` currently uses a vite-specific import. For now, inline it using the `toString()` pattern or a vite/rollup plugin. This is the trickiest part of this milestone. +- Dependencies: `@moq/hang`, `@moq/lite`, `@moq/signals`, `comlink`, `async-mutex`. +- Remove `./publish` and `./publish/element` exports from `hang/package.json`. +- Remove `export * as Publish from "./publish"` from `hang/src/index.ts`. + +**README updates:** +- Create a new `js/publish/README.md` documenting the `@moq/publish` package: installation, JS API, and web component usage (`` element and attributes). +- Pull the relevant content from `hang/README.md` sections: `` attributes, the publish portion of the JS API example, and tree-shaking notes. +- Update `hang/README.md`: + - Remove the `` section and its code examples. + - Remove publish-related JS API examples (`Hang.Publish.Broadcast`, etc.). + - Add a note pointing users to `@moq/publish` for publish functionality. + - After both M2 and M3, the "Web Components" and "Javascript API" sections should reference only `` or be replaced with a high-level overview pointing to the new packages. + +**Doc site updates:** +- Move `doc/js/@moq/hang/publish.md` → `doc/js/@moq/publish.md` (or similar), update all import paths from `@moq/hang/publish` to `@moq/publish`. +- Update `doc/js/@moq/hang/index.md` to remove publish sections and link to the new `@moq/publish` page. +- Update `doc/js/index.md` "Quick Start" examples to use `@moq/publish` imports. +- Update `doc/js/env/web.md` `` section: change imports from `@moq/hang/publish/element` to `@moq/publish/element`. +- Update `doc/.vitepress/config.ts` sidebar: move Publish out from under `@moq/hang` to a top-level `@moq/publish` entry. + +**Other updates:** +- Add `js/publish` to root `package.json` workspaces. +- Add `publish/` to `CLAUDE.md` project structure. +- Add `@moq/publish` row to root `README.md` package table. + +**Exit criteria:** `@moq/publish` builds with worklet inlined. `@moq/hang` builds without publish. + +> **Note:** Milestones 2 and 3 can be done in parallel. + +--- + +### Milestone 4: Move watch UI into `@moq/watch/ui` + +**Scope:** +- Move `hang-ui/src/watch/` → `watch/src/ui/`. +- Add SolidJS + `solid-element` + `vite-plugin-solid` as dev deps of `@moq/watch`. +- Update vite config to have two entry points: + - `watch/index` → `src/index.ts` (logic) + - `watch/ui` → `src/ui/index.tsx` (web component) +- Update UI imports: `@moq/hang/watch/element` → local `../element`, shared components → `@moq/ui-core`. +- Add `@moq/ui-core` as a dependency. +- Configure `rollupOptions.external` to externalize `@moq/hang`, `@moq/lite`, `@moq/signals`, `@moq/ui-core`. +- Export `./ui` in `package.json`. + +**README updates:** +- Update `js/watch/README.md` to add a "UI" section documenting the `@moq/watch/ui` entry point, how to use ``, and its dependency on `@moq/ui-core`. +- Update `hang-ui/README.md` to remove the `watch/` section from "Project Structure" and "Module Overview", noting it has moved to `@moq/watch/ui`. + +**Doc site updates:** +- Update `doc/js/@moq/watch.md` SolidJS integration section: change `@moq/hang-ui/watch` to `@moq/watch/ui`. + +**Exit criteria:** `@moq/watch` builds both logic and UI. The `hang-watch-ui` custom element registers and functions. + +> **Note:** Milestones 4 and 5 can be done in parallel. + +--- + +### Milestone 5: Move publish UI into `@moq/publish/ui` + +**Scope:** Mirror of Milestone 4 for publish: +- Move `hang-ui/src/publish/` → `publish/src/ui/`. +- Same vite/SolidJS setup as watch. +- Two entry points: `@moq/publish` (logic) and `@moq/publish/ui` (web component). +- Update imports similarly. + +**README updates:** +- Update `js/publish/README.md` to add a "UI" section documenting the `@moq/publish/ui` entry point, how to use ``, and its dependency on `@moq/ui-core`. +- Update `hang-ui/README.md` to remove the `publish/` section from "Project Structure" and "Module Overview", noting it has moved to `@moq/publish/ui`. + +**Doc site updates:** +- Update `doc/js/@moq/publish.md` SolidJS integration section: change `@moq/hang-ui/publish` to `@moq/publish/ui`. + +**Exit criteria:** `@moq/publish` builds both logic and UI. The `hang-publish-ui` custom element registers and functions. + +> **Note:** Milestones 4 and 5 can be done in parallel. + +--- + +### Milestone 6: Update `@moq/hang-demo` and remove `@moq/hang-ui` + +**Scope:** +- Update `hang-demo/package.json`: replace `@moq/hang-ui` with `@moq/watch`, `@moq/publish` (and `@moq/ui-core` if used directly). +- Update all imports in `hang-demo`: + - `@moq/hang-ui/watch` → `@moq/watch/ui` + - `@moq/hang-ui/publish` → `@moq/publish/ui` + - `@moq/hang/watch` → `@moq/watch` + - `@moq/hang/publish` → `@moq/publish` +- Delete `js/hang-ui/` entirely. +- Full end-to-end test of the demo app. + +**README updates:** +- Update `hang-demo/README.md` if it gains any references to specific packages (currently minimal). +- Delete `hang-ui/README.md` along with the `hang-ui` package. + +**Doc site updates:** +- Delete `doc/js/@moq/hang-ui.md`. +- Remove `@moq/hang-ui` sidebar entry from `doc/.vitepress/config.ts`. +- Remove `js/hang-ui` from root `package.json` workspaces. +- Update `doc/index.md` to replace `@moq/hang-ui` reference with `@moq/watch/ui` and `@moq/publish/ui`. +- Update `doc/js/index.md` to remove `@moq/hang-ui` section and install command; replace with `@moq/watch`, `@moq/publish`, `@moq/ui-core`. +- Update `doc/js/env/web.md` SolidJS section to reference `@moq/watch/ui` and `@moq/publish/ui` instead of `@moq/hang-ui`. +- Remove `hang-ui/` from `CLAUDE.md` project structure, update architecture description. +- Remove `@moq/hang-ui` row from root `README.md` package table. + +**Exit criteria:** `hang-demo` runs correctly with the new packages. `hang-ui` is gone. + +--- + +### Milestone 7: Clean up `@moq/hang` + +**Scope:** +- Delete `hang/src/watch/` and `hang/src/publish/` directories. +- Remove now-unused dependencies from `hang/package.json` (e.g. `@kixelated/libavjs-webcodecs-polyfill`, `@libav.js/variant-opus-af`, `comlink`, `async-mutex` — if only used by watch/publish). +- Remove `sideEffects` entries for watch/publish elements. +- Clean up `hang/src/index.ts` (should only export Catalog, Container, Support, and re-exports of Moq/Signals). +- Consider switching `@moq/hang` build from `tsc` to vite lib mode for consistency and jsdelivr support. +- Full build + test pass across all packages. + +**README updates:** +- Final rewrite of `hang/README.md`: + - Update the package description to reflect its new scope (catalog, container, support only). + - Remove the "Web Components" section entirely (or keep only ``). + - Replace the "Javascript API" section with catalog/container examples. + - Update the "Features" list to reflect core-only functionality. + - Add a "Related Packages" section linking to `@moq/watch`, `@moq/publish`, and `@moq/ui-core`. + +**Doc site updates:** +- Rewrite `doc/js/@moq/hang/index.md` to reflect core-only scope (catalog, container, support). Add links to `@moq/watch` and `@moq/publish` pages. +- Update `doc/js/index.md` description of `@moq/hang` in the "Core Libraries" section. +- Update `doc/js/env/web.md` production notes about Vite-only support (now applies to `@moq/watch`/`@moq/publish` instead of `@moq/hang`). +- Update `CLAUDE.md` architecture layer description for `hang`. +- Update root `README.md` description of `@moq/hang` in the package table. + +**Exit criteria:** `@moq/hang` is lean (catalog + container + support only). All packages build and tests pass. + +--- + +### Milestone 8: (Future) CDN / jsdelivr support + +**Scope:** +- Investigate vite/rollup plugin for proper AudioWorklet bundling (avoiding `toString()` hack). +- Set up `public/` directories in `@moq/watch` and `@moq/publish` for runtime-loadable assets. +- Add configurable `basePath` similar to [Shoelace](https://shoelace.style/getting-started/installation#setting-the-base-path). +- Test serving all packages via jsdelivr. +- Document the CDN usage pattern for consumers. + +**Exit criteria:** Packages are usable via ` @@ -114,7 +114,7 @@ Subscribes to a hang broadcast and renders it. ```html @@ -127,6 +127,8 @@ Subscribes to a hang broadcast and renders it. ``` +> **Note:** The `` element is now provided by [`@moq/watch`](../watch). See its [README](../watch/README.md) for full details. + ### `` @@ -184,8 +186,9 @@ const publish = new Hang.Publish.Broadcast(connection, { video: { enabled: true, device: "camera" }, }); -// Subscribing to media, with (optional) initial settings -const watch = new Hang.Watch.Broadcast(connection, { +// Subscribing to media — now in @moq/watch +import * as Watch from "@moq/watch"; +const watch = new Watch.Broadcast(connection, { enabled: true, name: "bob", video: { enabled: true }, diff --git a/js/hang/package.json b/js/hang/package.json index 77ae34d23..d001e0ae2 100644 --- a/js/hang/package.json +++ b/js/hang/package.json @@ -9,15 +9,15 @@ ".": "./src/index.ts", "./publish": "./src/publish/index.ts", "./publish/element": "./src/publish/element.ts", - "./watch": "./src/watch/index.ts", - "./watch/element": "./src/watch/element.ts", "./catalog": "./src/catalog/index.ts", + "./container": "./src/container/index.ts", + "./util/hex": "./src/util/hex.ts", + "./util/libav": "./src/util/libav.ts", "./support": "./src/support/index.ts", "./support/element": "./src/support/element.ts" }, "sideEffects": [ "./src/publish/element.ts", - "./src/watch/element.ts", "./src/support/element.ts" ], "scripts": { diff --git a/js/hang/src/index.ts b/js/hang/src/index.ts index fb940ca2b..2c0e5f417 100644 --- a/js/hang/src/index.ts +++ b/js/hang/src/index.ts @@ -4,4 +4,3 @@ export * as Catalog from "./catalog"; export * as Container from "./container"; export * as Publish from "./publish"; export * as Support from "./support"; -export * as Watch from "./watch"; diff --git a/js/ui-core/package.json b/js/ui-core/package.json index 71ccd8556..d9f0d7c32 100644 --- a/js/ui-core/package.json +++ b/js/ui-core/package.json @@ -21,7 +21,8 @@ }, "peerDependencies": { "@moq/hang": "workspace:^0.1.0", - "@moq/signals": "workspace:^0.1.0" + "@moq/signals": "workspace:^0.1.0", + "@moq/watch": "workspace:^0.1.0" }, "devDependencies": { "@typescript/lib-dom": "npm:@types/web@^0.0.241", diff --git a/js/ui-core/src/stats/types.ts b/js/ui-core/src/stats/types.ts index 275d1ef79..f46a25de7 100644 --- a/js/ui-core/src/stats/types.ts +++ b/js/ui-core/src/stats/types.ts @@ -1,6 +1,7 @@ export type KnownStatsProviders = "network" | "video" | "audio" | "buffer"; import type * as Hang from "@moq/hang"; +import type * as Watch from "@moq/watch"; /** * Context passed to providers for updating display data @@ -19,13 +20,13 @@ export interface VideoResolution { // TODO Don't re-export these types? export type Signal = Hang.Moq.Signals.Getter; -export type AudioStats = Hang.Watch.Audio.Stats; -export type AudioSource = Hang.Watch.Audio.Backend; +export type AudioStats = Watch.Audio.Stats; +export type AudioSource = Watch.Audio.Backend; export type AudioConfig = Hang.Catalog.AudioConfig; -export type VideoStats = Hang.Watch.Video.Stats; +export type VideoStats = Watch.Video.Stats; -// TODO use Hang.Watch.Backend instead? +// TODO use Watch.Backend instead? export type ProviderProps = { - audio: Hang.Watch.Audio.Backend; - video: Hang.Watch.Video.Backend; + audio: Watch.Audio.Backend; + video: Watch.Video.Backend; }; diff --git a/js/watch/README.md b/js/watch/README.md new file mode 100644 index 000000000..92e12ceb7 --- /dev/null +++ b/js/watch/README.md @@ -0,0 +1,83 @@ +

+ Media over QUIC +

+ +# @moq/watch + +[![npm](https://img.shields.io/npm/v/@moq/watch)](https://www.npmjs.com/package/@moq/watch) +[![TypeScript](https://img.shields.io/badge/TypeScript-ready-blue.svg)](https://www.typescriptlang.org/) + +Subscribe to and render [Media over QUIC](https://moq.dev/) (MoQ) broadcasts, built on top of [@moq/hang](../hang) and [@moq/lite](../lite). + +## Installation + +```bash +bun add @moq/watch +# or +npm add @moq/watch +``` + +## Web Component + +The simplest way to watch a stream: + +```html + + + + + +``` + +### Attributes + +| Attribute | Type | Default | Description | +|-----------|---------|----------|-----------------------| +| `url` | string | required | Relay server URL | +| `path` | string | required | Broadcast path | +| `paused` | boolean | false | Pause playback | +| `muted` | boolean | false | Mute audio | +| `volume` | number | 1 | Audio volume (0-1) | + +## JavaScript API + +For more control: + +```typescript +import * as Watch from "@moq/watch"; + +const watch = new Watch.Broadcast(connection, { + enabled: true, + name: "alice", + video: { enabled: true }, + audio: { enabled: true }, +}); + +// Access the video stream +watch.video.media.subscribe((stream) => { + if (stream) { + videoElement.srcObject = stream; + } +}); +``` + +## Features + +- **WebCodecs decoding** — Hardware-accelerated video and audio decoding +- **MSE fallback** — Media Source Extensions for broader codec support +- **Reactive state** — All properties are signals from `@moq/signals` +- **Chat** — Subscribe to text chat channels +- **Location** — Peer location and window tracking +- **Quality selection** — Switch between available renditions + +## License + +Licensed under either: + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) diff --git a/js/watch/package.json b/js/watch/package.json new file mode 100644 index 000000000..d1b8d7791 --- /dev/null +++ b/js/watch/package.json @@ -0,0 +1,31 @@ +{ + "name": "@moq/watch", + "type": "module", + "version": "0.1.0", + "description": "Watch/subscribe to Media over QUIC streams", + "license": "(MIT OR Apache-2.0)", + "repository": "github:moq-dev/moq", + "exports": { + ".": "./src/index.ts", + "./element": "./src/element.ts" + }, + "sideEffects": [ + "./src/element.ts" + ], + "scripts": { + "build": "rimraf dist && tsc -b && bun ../scripts/package.ts", + "check": "tsc --noEmit", + "release": "bun ../scripts/release.ts" + }, + "dependencies": { + "@moq/hang": "workspace:^", + "@moq/lite": "workspace:^", + "@moq/signals": "workspace:^" + }, + "devDependencies": { + "@types/audioworklet": "^0.0.77", + "@typescript/lib-dom": "npm:@types/web@^0.0.241", + "rimraf": "^6.0.1", + "typescript": "^5.9.2" + } +} diff --git a/js/hang/src/watch/audio/backend.ts b/js/watch/src/audio/backend.ts similarity index 100% rename from js/hang/src/watch/audio/backend.ts rename to js/watch/src/audio/backend.ts diff --git a/js/hang/src/watch/audio/decoder.ts b/js/watch/src/audio/decoder.ts similarity index 98% rename from js/hang/src/watch/audio/decoder.ts rename to js/watch/src/audio/decoder.ts index 18c9aaab8..94e2b8a2b 100644 --- a/js/hang/src/watch/audio/decoder.ts +++ b/js/watch/src/audio/decoder.ts @@ -1,10 +1,10 @@ import type * as Moq from "@moq/lite"; import { Time } from "@moq/lite"; import { Effect, type Getter, Signal } from "@moq/signals"; -import * as Catalog from "../../catalog"; -import * as Container from "../../container"; -import * as Hex from "../../util/hex"; -import * as libav from "../../util/libav"; +import * as Catalog from "@moq/hang/catalog"; +import * as Container from "@moq/hang/container"; +import * as Hex from "@moq/hang/util/hex"; +import * as libav from "@moq/hang/util/libav"; import type { BufferedRanges } from "../backend"; import type * as Render from "./render"; import type { ToMain } from "./render"; diff --git a/js/hang/src/watch/audio/emitter.ts b/js/watch/src/audio/emitter.ts similarity index 100% rename from js/hang/src/watch/audio/emitter.ts rename to js/watch/src/audio/emitter.ts diff --git a/js/hang/src/watch/audio/index.ts b/js/watch/src/audio/index.ts similarity index 100% rename from js/hang/src/watch/audio/index.ts rename to js/watch/src/audio/index.ts diff --git a/js/hang/src/watch/audio/mse.ts b/js/watch/src/audio/mse.ts similarity index 98% rename from js/hang/src/watch/audio/mse.ts rename to js/watch/src/audio/mse.ts index 96ecc304d..570c18224 100644 --- a/js/hang/src/watch/audio/mse.ts +++ b/js/watch/src/audio/mse.ts @@ -1,7 +1,7 @@ import * as Moq from "@moq/lite"; import { Effect, type Getter, Signal } from "@moq/signals"; -import * as Catalog from "../../catalog"; -import * as Container from "../../container"; +import * as Catalog from "@moq/hang/catalog"; +import * as Container from "@moq/hang/container"; import { type BufferedRanges, timeRangesToArray } from "../backend"; import type { Muxer } from "../mse"; import type { Backend, Stats } from "./backend"; diff --git a/js/hang/src/watch/audio/render-worklet.ts b/js/watch/src/audio/render-worklet.ts similarity index 100% rename from js/hang/src/watch/audio/render-worklet.ts rename to js/watch/src/audio/render-worklet.ts diff --git a/js/hang/src/watch/audio/render.ts b/js/watch/src/audio/render.ts similarity index 100% rename from js/hang/src/watch/audio/render.ts rename to js/watch/src/audio/render.ts diff --git a/js/hang/src/watch/audio/ring-buffer.test.ts b/js/watch/src/audio/ring-buffer.test.ts similarity index 100% rename from js/hang/src/watch/audio/ring-buffer.test.ts rename to js/watch/src/audio/ring-buffer.test.ts diff --git a/js/hang/src/watch/audio/ring-buffer.ts b/js/watch/src/audio/ring-buffer.ts similarity index 100% rename from js/hang/src/watch/audio/ring-buffer.ts rename to js/watch/src/audio/ring-buffer.ts diff --git a/js/hang/src/watch/audio/source.ts b/js/watch/src/audio/source.ts similarity index 98% rename from js/hang/src/watch/audio/source.ts rename to js/watch/src/audio/source.ts index 4bebdae42..d42b607d2 100644 --- a/js/hang/src/watch/audio/source.ts +++ b/js/watch/src/audio/source.ts @@ -1,6 +1,6 @@ import type * as Moq from "@moq/lite"; import { Effect, type Getter, Signal } from "@moq/signals"; -import type * as Catalog from "../../catalog"; +import type * as Catalog from "@moq/hang/catalog"; import type { Broadcast } from "../broadcast"; import type { Sync } from "../sync"; diff --git a/js/hang/src/watch/backend.ts b/js/watch/src/backend.ts similarity index 100% rename from js/hang/src/watch/backend.ts rename to js/watch/src/backend.ts diff --git a/js/hang/src/watch/broadcast.ts b/js/watch/src/broadcast.ts similarity index 98% rename from js/hang/src/watch/broadcast.ts rename to js/watch/src/broadcast.ts index 791570716..9e6893d7d 100644 --- a/js/hang/src/watch/broadcast.ts +++ b/js/watch/src/broadcast.ts @@ -1,6 +1,6 @@ import type * as Moq from "@moq/lite"; import { Effect, type Getter, Signal } from "@moq/signals"; -import * as Catalog from "../catalog"; +import * as Catalog from "@moq/hang/catalog"; export interface BroadcastProps { connection?: Moq.Connection.Established | Signal; diff --git a/js/hang/src/watch/chat/index.ts b/js/watch/src/chat/index.ts similarity index 95% rename from js/hang/src/watch/chat/index.ts rename to js/watch/src/chat/index.ts index c27e0dd7b..a3d82eec6 100644 --- a/js/hang/src/watch/chat/index.ts +++ b/js/watch/src/chat/index.ts @@ -1,6 +1,6 @@ import type * as Moq from "@moq/lite"; import { Effect, Signal } from "@moq/signals"; -import type * as Catalog from "../../catalog"; +import type * as Catalog from "@moq/hang/catalog"; import { Message, type MessageProps } from "./message"; import { Typing, type TypingProps } from "./typing"; diff --git a/js/hang/src/watch/chat/message.ts b/js/watch/src/chat/message.ts similarity index 97% rename from js/hang/src/watch/chat/message.ts rename to js/watch/src/chat/message.ts index 41959c1c3..64045f024 100644 --- a/js/hang/src/watch/chat/message.ts +++ b/js/watch/src/chat/message.ts @@ -1,6 +1,6 @@ import type * as Moq from "@moq/lite"; import { Effect, type Getter, Signal } from "@moq/signals"; -import * as Catalog from "../../catalog"; +import * as Catalog from "@moq/hang/catalog"; export interface MessageProps { // Whether to start downloading the chat. diff --git a/js/hang/src/watch/chat/typing.ts b/js/watch/src/chat/typing.ts similarity index 97% rename from js/hang/src/watch/chat/typing.ts rename to js/watch/src/chat/typing.ts index f4da07394..6d24ea9c4 100644 --- a/js/hang/src/watch/chat/typing.ts +++ b/js/watch/src/chat/typing.ts @@ -1,6 +1,6 @@ import type * as Moq from "@moq/lite"; import { Effect, type Getter, Signal } from "@moq/signals"; -import * as Catalog from "../../catalog"; +import * as Catalog from "@moq/hang/catalog"; export interface TypingProps { // Whether to start downloading the chat. diff --git a/js/hang/src/watch/element.ts b/js/watch/src/element.ts similarity index 100% rename from js/hang/src/watch/element.ts rename to js/watch/src/element.ts diff --git a/js/hang/src/watch/index.ts b/js/watch/src/index.ts similarity index 82% rename from js/hang/src/watch/index.ts rename to js/watch/src/index.ts index 5decc45f4..22994dfeb 100644 --- a/js/hang/src/watch/index.ts +++ b/js/watch/src/index.ts @@ -8,4 +8,4 @@ export * from "./preview"; export * as Video from "./video"; // NOTE: element is not exported from this module -// You have to import it from @moq/hang/watch/element instead. +// You have to import it from @moq/watch/element instead. diff --git a/js/hang/src/watch/location/index.ts b/js/watch/src/location/index.ts similarity index 93% rename from js/hang/src/watch/location/index.ts rename to js/watch/src/location/index.ts index 5fed5b1dc..a3ce9e814 100644 --- a/js/hang/src/watch/location/index.ts +++ b/js/watch/src/location/index.ts @@ -1,6 +1,6 @@ import type * as Moq from "@moq/lite"; import { Effect, type Signal } from "@moq/signals"; -import type * as Catalog from "../../catalog"; +import type * as Catalog from "@moq/hang/catalog"; import { Peers, type PeersProps } from "./peers"; import { Window, type WindowProps } from "./window"; diff --git a/js/hang/src/watch/location/peers.ts b/js/watch/src/location/peers.ts similarity index 97% rename from js/hang/src/watch/location/peers.ts rename to js/watch/src/location/peers.ts index f83a44253..c50c1e857 100644 --- a/js/hang/src/watch/location/peers.ts +++ b/js/watch/src/location/peers.ts @@ -1,7 +1,7 @@ import type * as Moq from "@moq/lite"; import * as Zod from "@moq/lite/zod"; import { Effect, type Getter, Signal } from "@moq/signals"; -import * as Catalog from "../../catalog"; +import * as Catalog from "@moq/hang/catalog"; export interface PeersProps { enabled?: boolean | Signal; diff --git a/js/hang/src/watch/location/window.ts b/js/watch/src/location/window.ts similarity index 97% rename from js/hang/src/watch/location/window.ts rename to js/watch/src/location/window.ts index 4b743be64..f9c8d8d7e 100644 --- a/js/hang/src/watch/location/window.ts +++ b/js/watch/src/location/window.ts @@ -1,7 +1,7 @@ import type * as Moq from "@moq/lite"; import * as Zod from "@moq/lite/zod"; import { Effect, type Getter, Signal } from "@moq/signals"; -import * as Catalog from "../../catalog"; +import * as Catalog from "@moq/hang/catalog"; export interface WindowProps { enabled?: boolean | Signal; diff --git a/js/hang/src/watch/mse.ts b/js/watch/src/mse.ts similarity index 100% rename from js/hang/src/watch/mse.ts rename to js/watch/src/mse.ts diff --git a/js/hang/src/watch/preview.ts b/js/watch/src/preview.ts similarity index 96% rename from js/hang/src/watch/preview.ts rename to js/watch/src/preview.ts index 8b451112f..78c7cae54 100644 --- a/js/hang/src/watch/preview.ts +++ b/js/watch/src/preview.ts @@ -1,7 +1,7 @@ import type * as Moq from "@moq/lite"; import * as Zod from "@moq/lite/zod"; import { Effect, Signal } from "@moq/signals"; -import * as Catalog from "../catalog"; +import * as Catalog from "@moq/hang/catalog"; export interface PreviewProps { enabled?: boolean | Signal; diff --git a/js/hang/src/watch/sync.ts b/js/watch/src/sync.ts similarity index 100% rename from js/hang/src/watch/sync.ts rename to js/watch/src/sync.ts diff --git a/js/hang/src/watch/user.ts b/js/watch/src/user.ts similarity index 95% rename from js/hang/src/watch/user.ts rename to js/watch/src/user.ts index f3f638bd5..7ed217ef9 100644 --- a/js/hang/src/watch/user.ts +++ b/js/watch/src/user.ts @@ -1,5 +1,5 @@ import { Effect, type Getter, Signal } from "@moq/signals"; -import type * as Catalog from "../catalog"; +import type * as Catalog from "@moq/hang/catalog"; export interface Props { enabled?: boolean | Signal; diff --git a/js/hang/src/watch/video/backend.ts b/js/watch/src/video/backend.ts similarity index 100% rename from js/hang/src/watch/video/backend.ts rename to js/watch/src/video/backend.ts diff --git a/js/hang/src/watch/video/decoder.ts b/js/watch/src/video/decoder.ts similarity index 98% rename from js/hang/src/watch/video/decoder.ts rename to js/watch/src/video/decoder.ts index e9cdc5400..db4e5a8c1 100644 --- a/js/hang/src/watch/video/decoder.ts +++ b/js/watch/src/video/decoder.ts @@ -1,9 +1,9 @@ import type * as Moq from "@moq/lite"; import { Time } from "@moq/lite"; import { Effect, type Getter, Signal } from "@moq/signals"; -import * as Catalog from "../../catalog"; -import * as Container from "../../container"; -import * as Hex from "../../util/hex"; +import * as Catalog from "@moq/hang/catalog"; +import * as Container from "@moq/hang/container"; +import * as Hex from "@moq/hang/util/hex"; import type { BufferedRanges } from "../backend"; import type { Backend, Stats } from "./backend"; import type { Source } from "./source"; diff --git a/js/hang/src/watch/video/index.ts b/js/watch/src/video/index.ts similarity index 100% rename from js/hang/src/watch/video/index.ts rename to js/watch/src/video/index.ts diff --git a/js/hang/src/watch/video/mse.ts b/js/watch/src/video/mse.ts similarity index 98% rename from js/hang/src/watch/video/mse.ts rename to js/watch/src/video/mse.ts index 1e8c8fd74..11bcedd29 100644 --- a/js/hang/src/watch/video/mse.ts +++ b/js/watch/src/video/mse.ts @@ -1,7 +1,7 @@ import * as Moq from "@moq/lite"; import { Effect, type Getter, Signal } from "@moq/signals"; -import * as Catalog from "../../catalog"; -import * as Container from "../../container"; +import * as Catalog from "@moq/hang/catalog"; +import * as Container from "@moq/hang/container"; import { type BufferedRanges, timeRangesToArray } from "../backend"; import type { Muxer } from "../mse"; import type { Backend, Stats } from "./backend"; diff --git a/js/hang/src/watch/video/renderer.ts b/js/watch/src/video/renderer.ts similarity index 100% rename from js/hang/src/watch/video/renderer.ts rename to js/watch/src/video/renderer.ts diff --git a/js/hang/src/watch/video/source.ts b/js/watch/src/video/source.ts similarity index 98% rename from js/hang/src/watch/video/source.ts rename to js/watch/src/video/source.ts index 68bd6b858..3d35d772b 100644 --- a/js/hang/src/watch/video/source.ts +++ b/js/watch/src/video/source.ts @@ -1,6 +1,6 @@ import type * as Moq from "@moq/lite"; import { Effect, type Getter, Signal } from "@moq/signals"; -import type * as Catalog from "../../catalog"; +import type * as Catalog from "@moq/hang/catalog"; import type { Broadcast } from "../broadcast"; import type { Sync } from "../sync"; diff --git a/js/watch/src/worklet.d.ts b/js/watch/src/worklet.d.ts new file mode 100644 index 000000000..7bb79c31d --- /dev/null +++ b/js/watch/src/worklet.d.ts @@ -0,0 +1,4 @@ +declare module "*-worklet.ts?worker&url" { + const url: string; + export default url; +} diff --git a/js/watch/tsconfig.json b/js/watch/tsconfig.json new file mode 100644 index 000000000..8a2f570f1 --- /dev/null +++ b/js/watch/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "dist", + "composite": true + }, + "include": ["src"] +} diff --git a/package.json b/package.json index 8f33bb1f3..0ffe1f714 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "js/token", "js/hang", "js/ui-core", + "js/watch", "js/hang-ui", "js/hang-demo", "js/signals" From 8173211d0ef21f7a8adf0fe8247572fa31802e02 Mon Sep 17 00:00:00 2001 From: James Reetzke Date: Wed, 11 Feb 2026 22:54:44 -0500 Subject: [PATCH 03/17] refactor: extract publish logic into @moq/publish package (Milestone 3) Move the publish module from @moq/hang into a standalone @moq/publish package. This separates publish functionality so it can be consumed independently, mirroring the @moq/watch extraction in Milestone 2. Files changed: - js/publish/ (new): New @moq/publish package with tsc build, containing all 26 publish source files (broadcast, preview, user, audio/*, video/*, chat/*, location/*, source/*), plus worklet.d.ts for vite worker imports, and README documenting the Web Component and JS API. - js/hang/src/publish/ (deleted): Moved entirely into @moq/publish. - js/hang/package.json: Removed ./publish and ./publish/element exports; added ./util/hacks export so @moq/publish can import isFirefox. Removed publish from sideEffects array. - js/hang/src/index.ts: Removed 'export * as Publish' barrel re-export since publish is now its own package. - js/hang/README.md: Updated import examples and JS API code samples to reference @moq/publish/element and @moq/publish instead of @moq/hang/publish. Added notes pointing to @moq/publish README. - js/publish/src/{broadcast,preview,user}.ts: Updated catalog imports from relative ../catalog to @moq/hang/catalog. - js/publish/src/{chat,location}/*.ts: Updated catalog imports from relative ../../catalog to @moq/hang/catalog. - js/publish/src/audio/encoder.ts: Updated catalog, container, and libav imports from relative ../../* to @moq/hang/*. - js/publish/src/video/encoder.ts: Updated catalog, container, and hacks imports from relative ../../* to @moq/hang/*. - js/publish/src/video/index.ts: Updated catalog import to @moq/hang/catalog. - js/publish/src/location/index.ts: Changed barrel import 'import { Catalog } from "../.."' to 'import * as Catalog from @moq/hang/catalog'. - js/publish/src/index.ts: Updated comment to reference @moq/publish/element instead of @moq/hang/publish/element. - js/hang-ui/src/publish/{context,element,index}.tsx: Updated imports from @moq/hang/publish/element to @moq/publish/element. - js/hang-ui/package.json: Added @moq/publish as peer dependency. - js/hang-ui/vite.config.ts: Added @moq/publish to rollup externals. - js/hang-demo/src/publish.ts: Updated HangPublish import from @moq/hang/publish/element to @moq/publish/element. - js/hang-demo/package.json: Added @moq/publish as dependency. - package.json (root): Added js/publish to workspaces array. - CLAUDE.md: Added publish/ to the JS project structure listing. - README.md (root): Added @moq/publish row to the TypeScript packages table. --- CLAUDE.md | 1 + README.md | 1 + bun.lock | 19 +++++ js/hang-demo/package.json | 3 +- js/hang-demo/src/publish.ts | 2 +- js/hang-ui/package.json | 3 +- js/hang-ui/src/publish/context.tsx | 2 +- js/hang-ui/src/publish/element.tsx | 2 +- js/hang-ui/src/publish/index.tsx | 2 +- js/hang-ui/vite.config.ts | 2 +- js/hang/README.md | 11 ++- js/hang/package.json | 4 +- js/hang/src/index.ts | 1 - js/publish/README.md | 81 +++++++++++++++++++ js/publish/package.json | 31 +++++++ .../src}/audio/capture-worklet.ts | 0 .../publish => publish/src}/audio/capture.ts | 0 .../publish => publish/src}/audio/encoder.ts | 6 +- .../publish => publish/src}/audio/index.ts | 0 .../publish => publish/src}/audio/types.ts | 0 .../src/publish => publish/src}/broadcast.ts | 2 +- .../src/publish => publish/src}/chat/index.ts | 2 +- .../publish => publish/src}/chat/message.ts | 2 +- .../publish => publish/src}/chat/typing.ts | 2 +- .../src/publish => publish/src}/element.ts | 0 js/{hang/src/publish => publish/src}/index.ts | 2 +- .../publish => publish/src}/location/index.ts | 2 +- .../publish => publish/src}/location/peers.ts | 2 +- .../src}/location/window.ts | 2 +- .../src/publish => publish/src}/preview.ts | 2 +- .../publish => publish/src}/source/camera.ts | 0 .../publish => publish/src}/source/device.ts | 0 .../publish => publish/src}/source/file.ts | 0 .../publish => publish/src}/source/index.ts | 0 .../src}/source/microphone.ts | 0 .../publish => publish/src}/source/screen.ts | 0 js/{hang/src/publish => publish/src}/user.ts | 2 +- .../publish => publish/src}/video/encoder.ts | 6 +- .../publish => publish/src}/video/index.ts | 2 +- .../publish => publish/src}/video/polyfill.ts | 0 .../publish => publish/src}/video/types.ts | 0 js/publish/src/worklet.d.ts | 4 + js/publish/tsconfig.json | 9 +++ package.json | 1 + 44 files changed, 181 insertions(+), 32 deletions(-) create mode 100644 js/publish/README.md create mode 100644 js/publish/package.json rename js/{hang/src/publish => publish/src}/audio/capture-worklet.ts (100%) rename js/{hang/src/publish => publish/src}/audio/capture.ts (100%) rename js/{hang/src/publish => publish/src}/audio/encoder.ts (97%) rename js/{hang/src/publish => publish/src}/audio/index.ts (100%) rename js/{hang/src/publish => publish/src}/audio/types.ts (100%) rename js/{hang/src/publish => publish/src}/broadcast.ts (98%) rename js/{hang/src/publish => publish/src}/chat/index.ts (94%) rename js/{hang/src/publish => publish/src}/chat/message.ts (95%) rename js/{hang/src/publish => publish/src}/chat/typing.ts (95%) rename js/{hang/src/publish => publish/src}/element.ts (100%) rename js/{hang/src/publish => publish/src}/index.ts (82%) rename js/{hang/src/publish => publish/src}/location/index.ts (94%) rename js/{hang/src/publish => publish/src}/location/peers.ts (96%) rename js/{hang/src/publish => publish/src}/location/window.ts (97%) rename js/{hang/src/publish => publish/src}/preview.ts (95%) rename js/{hang/src/publish => publish/src}/source/camera.ts (100%) rename js/{hang/src/publish => publish/src}/source/device.ts (100%) rename js/{hang/src/publish => publish/src}/source/file.ts (100%) rename js/{hang/src/publish => publish/src}/source/index.ts (100%) rename js/{hang/src/publish => publish/src}/source/microphone.ts (100%) rename js/{hang/src/publish => publish/src}/source/screen.ts (100%) rename js/{hang/src/publish => publish/src}/user.ts (95%) rename js/{hang/src/publish => publish/src}/video/encoder.ts (98%) rename js/{hang/src/publish => publish/src}/video/index.ts (98%) rename js/{hang/src/publish => publish/src}/video/polyfill.ts (100%) rename js/{hang/src/publish => publish/src}/video/types.ts (100%) create mode 100644 js/publish/src/worklet.d.ts create mode 100644 js/publish/tsconfig.json diff --git a/CLAUDE.md b/CLAUDE.md index 66f8a8be4..3ffb8ec11 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -59,6 +59,7 @@ Key architectural rule: The CDN/relay does not know anything about media. Anythi hang/ # Media layer (published as @moq/hang) ui-core/ # Shared UI components (published as @moq/ui-core) watch/ # Watch/subscribe to streams (published as @moq/watch) + publish/ # Publish media to streams (published as @moq/publish) hang-ui/ # Web Components UI (published as @moq/hang-ui) hang-demo/ # Demo applications diff --git a/README.md b/README.md index 0c6583472..41e91c3da 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ This repository provides both [Rust](/rs) and [TypeScript](/js) libraries with s | **[@moq/hang](js/hang)** | Media-specific encoding/streaming layered on top of `moq-lite`. Provides both a Javascript API and Web Components. | [![npm](https://img.shields.io/npm/v/@moq/hang)](https://www.npmjs.com/package/@moq/hang) | | **[@moq/hang-demo](js/hang-demo)** | Examples using `@moq/hang`. | | | **[@moq/watch](js/watch)** | Subscribe to and render MoQ broadcasts (Web Component + JS API). | [![npm](https://img.shields.io/npm/v/@moq/watch)](https://www.npmjs.com/package/@moq/watch) | +| **[@moq/publish](js/publish)** | Publish media to MoQ broadcasts (Web Component + JS API). | [![npm](https://img.shields.io/npm/v/@moq/publish)](https://www.npmjs.com/package/@moq/publish) | | **[@moq/ui-core](js/ui-core)** | Shared UI components (Button, Icon, Stats, CSS theme) used by hang-ui. | [![npm](https://img.shields.io/npm/v/@moq/ui-core)](https://www.npmjs.com/package/@moq/ui-core) | | **[@moq/hang-ui](js/hang-ui)**. | UI Components that interact with the Hang Web Components using SolidJS. | [![npm](https://img.shields.io/npm/v/@moq/hang-ui)](https://www.npmjs.com/package/@moq/hang-ui) | diff --git a/bun.lock b/bun.lock index 997483d7e..2cffebc28 100644 --- a/bun.lock +++ b/bun.lock @@ -61,6 +61,7 @@ "dependencies": { "@moq/hang": "workspace:^", "@moq/hang-ui": "workspace:^", + "@moq/publish": "workspace:^", "@moq/watch": "workspace:^", }, "devDependencies": { @@ -89,6 +90,7 @@ }, "peerDependencies": { "@moq/hang": "workspace:^0.1.0", + "@moq/publish": "workspace:^0.1.0", "@moq/signals": "workspace:^0.1.0", "@moq/ui-core": "workspace:^0.1.0", "@moq/watch": "workspace:^0.1.0", @@ -115,6 +117,21 @@ "zod": "^4.1.0", }, }, + "js/publish": { + "name": "@moq/publish", + "version": "0.1.0", + "dependencies": { + "@moq/hang": "workspace:^", + "@moq/lite": "workspace:^", + "@moq/signals": "workspace:^", + }, + "devDependencies": { + "@types/audioworklet": "^0.0.77", + "@typescript/lib-dom": "npm:@types/web@^0.0.241", + "rimraf": "^6.0.1", + "typescript": "^5.9.2", + }, + }, "js/signals": { "name": "@moq/signals", "version": "0.1.2", @@ -442,6 +459,8 @@ "@moq/lite": ["@moq/lite@workspace:js/lite"], + "@moq/publish": ["@moq/publish@workspace:js/publish"], + "@moq/signals": ["@moq/signals@workspace:js/signals"], "@moq/token": ["@moq/token@workspace:js/token"], diff --git a/js/hang-demo/package.json b/js/hang-demo/package.json index a7a7f8135..6934f30d4 100644 --- a/js/hang-demo/package.json +++ b/js/hang-demo/package.json @@ -14,7 +14,8 @@ "dependencies": { "@moq/hang": "workspace:^", "@moq/hang-ui": "workspace:^", - "@moq/watch": "workspace:^" + "@moq/watch": "workspace:^", + "@moq/publish": "workspace:^" }, "devDependencies": { "@tailwindcss/typography": "^0.5.16", diff --git a/js/hang-demo/src/publish.ts b/js/hang-demo/src/publish.ts index da2665f20..5532a27c6 100644 --- a/js/hang-demo/src/publish.ts +++ b/js/hang-demo/src/publish.ts @@ -2,7 +2,7 @@ import "./highlight"; import "@moq/hang-ui/publish"; // We need to import Web Components with fully-qualified paths because of tree-shaking. -import HangPublish from "@moq/hang/publish/element"; +import HangPublish from "@moq/publish/element"; import HangSupport from "@moq/hang/support/element"; export { HangPublish, HangSupport }; diff --git a/js/hang-ui/package.json b/js/hang-ui/package.json index 0b0b910bf..f79353687 100644 --- a/js/hang-ui/package.json +++ b/js/hang-ui/package.json @@ -24,7 +24,8 @@ "@moq/hang": "workspace:^0.1.0", "@moq/signals": "workspace:^0.1.0", "@moq/ui-core": "workspace:^0.1.0", - "@moq/watch": "workspace:^0.1.0" + "@moq/watch": "workspace:^0.1.0", + "@moq/publish": "workspace:^0.1.0" }, "devDependencies": { "@types/audioworklet": "^0.0.77", diff --git a/js/hang-ui/src/publish/context.tsx b/js/hang-ui/src/publish/context.tsx index 472dbc582..4d0f23f02 100644 --- a/js/hang-ui/src/publish/context.tsx +++ b/js/hang-ui/src/publish/context.tsx @@ -1,4 +1,4 @@ -import type HangPublish from "@moq/hang/publish/element"; +import type HangPublish from "@moq/publish/element"; import type { JSX } from "solid-js"; import { createContext, createEffect, createSignal } from "solid-js"; diff --git a/js/hang-ui/src/publish/element.tsx b/js/hang-ui/src/publish/element.tsx index 91974f173..915ba63f7 100644 --- a/js/hang-ui/src/publish/element.tsx +++ b/js/hang-ui/src/publish/element.tsx @@ -1,4 +1,4 @@ -import type HangPublish from "@moq/hang/publish/element"; +import type HangPublish from "@moq/publish/element"; import PublishControls from "./components/PublishControls"; import PublishControlsContextProvider from "./context"; import styles from "./styles/index.css?inline"; diff --git a/js/hang-ui/src/publish/index.tsx b/js/hang-ui/src/publish/index.tsx index 509ab33a8..4856300d8 100644 --- a/js/hang-ui/src/publish/index.tsx +++ b/js/hang-ui/src/publish/index.tsx @@ -1,4 +1,4 @@ -import type HangPublish from "@moq/hang/publish/element"; +import type HangPublish from "@moq/publish/element"; import { customElement } from "solid-element"; import { createSignal, onMount } from "solid-js"; import { Show } from "solid-js/web"; diff --git a/js/hang-ui/vite.config.ts b/js/hang-ui/vite.config.ts index 93c65c2a6..e7d6dd206 100644 --- a/js/hang-ui/vite.config.ts +++ b/js/hang-ui/vite.config.ts @@ -13,7 +13,7 @@ export default defineConfig({ formats: ["es"], }, rollupOptions: { - external: ["@moq/hang", "@moq/lite", "@moq/signals", "@moq/ui-core", "@moq/watch"], + external: ["@moq/hang", "@moq/lite", "@moq/signals", "@moq/ui-core", "@moq/watch", "@moq/publish"], }, sourcemap: true, target: "esnext", diff --git a/js/hang/README.md b/js/hang/README.md index ffdb498b1..5a725a1d5 100644 --- a/js/hang/README.md +++ b/js/hang/README.md @@ -44,7 +44,7 @@ There's also a Javascript API for more advanced use cases; see below. @@ -144,7 +144,7 @@ Publishes a microphone/camera or screen as a hang broadcast. ```html ``` +> **Note:** The `` element is now provided by [`@moq/publish`](../publish). See its [README](../publish/README.md) for full details. + ### `` A simple element that displays browser support. @@ -179,8 +181,9 @@ import * as Hang from "@moq/hang"; // Create a new connection, available via `.established` const connection = new Hang.Connection("https://cdn.moq.dev/anon"); -// Publishing media, with (optional) initial settings -const publish = new Hang.Publish.Broadcast(connection, { +// Publishing media — now in @moq/publish +import * as Publish from "@moq/publish"; +const publish = new Publish.Broadcast(connection, { enabled: true, name: "bob", video: { enabled: true, device: "camera" }, diff --git a/js/hang/package.json b/js/hang/package.json index d001e0ae2..5f6689f9a 100644 --- a/js/hang/package.json +++ b/js/hang/package.json @@ -7,17 +7,15 @@ "repository": "github:moq-dev/moq", "exports": { ".": "./src/index.ts", - "./publish": "./src/publish/index.ts", - "./publish/element": "./src/publish/element.ts", "./catalog": "./src/catalog/index.ts", "./container": "./src/container/index.ts", "./util/hex": "./src/util/hex.ts", "./util/libav": "./src/util/libav.ts", + "./util/hacks": "./src/util/hacks.ts", "./support": "./src/support/index.ts", "./support/element": "./src/support/element.ts" }, "sideEffects": [ - "./src/publish/element.ts", "./src/support/element.ts" ], "scripts": { diff --git a/js/hang/src/index.ts b/js/hang/src/index.ts index 2c0e5f417..5144c019f 100644 --- a/js/hang/src/index.ts +++ b/js/hang/src/index.ts @@ -2,5 +2,4 @@ export * as Moq from "@moq/lite"; export * as Signals from "@moq/signals"; export * as Catalog from "./catalog"; export * as Container from "./container"; -export * as Publish from "./publish"; export * as Support from "./support"; diff --git a/js/publish/README.md b/js/publish/README.md new file mode 100644 index 000000000..03d6fd5e4 --- /dev/null +++ b/js/publish/README.md @@ -0,0 +1,81 @@ +

+ Media over QUIC +

+ +# @moq/publish + +[![npm](https://img.shields.io/npm/v/@moq/publish)](https://www.npmjs.com/package/@moq/publish) +[![TypeScript](https://img.shields.io/badge/TypeScript-ready-blue.svg)](https://www.typescriptlang.org/) + +Publish media to [Media over QUIC](https://moq.dev/) (MoQ) broadcasts, built on top of [@moq/hang](../hang) and [@moq/lite](../lite). + +## Installation + +```bash +bun add @moq/publish +# or +npm add @moq/publish +``` + +## Web Component + +The simplest way to publish a stream: + +```html + + + + + +``` + +### Attributes + +| Attribute | Type | Default | Description | +|------------|---------|----------|---------------------------------| +| `url` | string | required | Relay server URL | +| `path` | string | required | Broadcast path | +| `source` | string | — | `"camera"`, `"screen"`, `"file"` | +| `audio` | boolean | false | Enable audio capture | +| `video` | boolean | false | Enable video capture | +| `controls` | boolean | false | Show simple publishing controls | + +## JavaScript API + +For more control: + +```typescript +import * as Publish from "@moq/publish"; + +const publish = new Publish.Broadcast(connection, { + enabled: true, + name: "alice", + video: { enabled: true }, + audio: { enabled: true }, +}); + +// Change source at runtime +publish.source.camera.enabled.set(true); +``` + +## Features + +- **Camera & microphone** — Capture from user devices +- **Screen sharing** — Capture display or window +- **File playback** — Publish from a media file +- **WebCodecs encoding** — Hardware-accelerated video and audio encoding +- **Reactive state** — All properties are signals from `@moq/signals` +- **Chat** — Publish text chat messages +- **Location** — Publish peer position and window tracking + +## License + +Licensed under either: + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) diff --git a/js/publish/package.json b/js/publish/package.json new file mode 100644 index 000000000..46120e40b --- /dev/null +++ b/js/publish/package.json @@ -0,0 +1,31 @@ +{ + "name": "@moq/publish", + "type": "module", + "version": "0.1.0", + "description": "Publish media to Media over QUIC streams", + "license": "(MIT OR Apache-2.0)", + "repository": "github:moq-dev/moq", + "exports": { + ".": "./src/index.ts", + "./element": "./src/element.ts" + }, + "sideEffects": [ + "./src/element.ts" + ], + "scripts": { + "build": "rimraf dist && tsc -b && bun ../scripts/package.ts", + "check": "tsc --noEmit", + "release": "bun ../scripts/release.ts" + }, + "dependencies": { + "@moq/hang": "workspace:^", + "@moq/lite": "workspace:^", + "@moq/signals": "workspace:^" + }, + "devDependencies": { + "@types/audioworklet": "^0.0.77", + "@typescript/lib-dom": "npm:@types/web@^0.0.241", + "rimraf": "^6.0.1", + "typescript": "^5.9.2" + } +} diff --git a/js/hang/src/publish/audio/capture-worklet.ts b/js/publish/src/audio/capture-worklet.ts similarity index 100% rename from js/hang/src/publish/audio/capture-worklet.ts rename to js/publish/src/audio/capture-worklet.ts diff --git a/js/hang/src/publish/audio/capture.ts b/js/publish/src/audio/capture.ts similarity index 100% rename from js/hang/src/publish/audio/capture.ts rename to js/publish/src/audio/capture.ts diff --git a/js/hang/src/publish/audio/encoder.ts b/js/publish/src/audio/encoder.ts similarity index 97% rename from js/hang/src/publish/audio/encoder.ts rename to js/publish/src/audio/encoder.ts index 706f3a425..71957304e 100644 --- a/js/hang/src/publish/audio/encoder.ts +++ b/js/publish/src/audio/encoder.ts @@ -1,9 +1,9 @@ import type * as Moq from "@moq/lite"; import { Time } from "@moq/lite"; import { Effect, type Getter, Signal } from "@moq/signals"; -import * as Catalog from "../../catalog"; -import * as Container from "../../container"; -import * as libav from "../../util/libav"; +import * as Catalog from "@moq/hang/catalog"; +import * as Container from "@moq/hang/container"; +import * as libav from "@moq/hang/util/libav"; import type * as Capture from "./capture"; import type { Source } from "./types"; diff --git a/js/hang/src/publish/audio/index.ts b/js/publish/src/audio/index.ts similarity index 100% rename from js/hang/src/publish/audio/index.ts rename to js/publish/src/audio/index.ts diff --git a/js/hang/src/publish/audio/types.ts b/js/publish/src/audio/types.ts similarity index 100% rename from js/hang/src/publish/audio/types.ts rename to js/publish/src/audio/types.ts diff --git a/js/hang/src/publish/broadcast.ts b/js/publish/src/broadcast.ts similarity index 98% rename from js/hang/src/publish/broadcast.ts rename to js/publish/src/broadcast.ts index bc0731f32..366adf43e 100644 --- a/js/hang/src/publish/broadcast.ts +++ b/js/publish/src/broadcast.ts @@ -1,6 +1,6 @@ import * as Moq from "@moq/lite"; import { Effect, Signal } from "@moq/signals"; -import * as Catalog from "../catalog"; +import * as Catalog from "@moq/hang/catalog"; import * as Audio from "./audio"; import * as Chat from "./chat"; import * as Location from "./location"; diff --git a/js/hang/src/publish/chat/index.ts b/js/publish/src/chat/index.ts similarity index 94% rename from js/hang/src/publish/chat/index.ts rename to js/publish/src/chat/index.ts index 0d97df30e..111194f84 100644 --- a/js/hang/src/publish/chat/index.ts +++ b/js/publish/src/chat/index.ts @@ -1,5 +1,5 @@ import { Effect, type Getter, Signal } from "@moq/signals"; -import type * as Catalog from "../../catalog"; +import type * as Catalog from "@moq/hang/catalog"; import { Message, type MessageProps } from "./message"; import { Typing, type TypingProps } from "./typing"; diff --git a/js/hang/src/publish/chat/message.ts b/js/publish/src/chat/message.ts similarity index 95% rename from js/hang/src/publish/chat/message.ts rename to js/publish/src/chat/message.ts index 91010e63a..3119fa5bf 100644 --- a/js/hang/src/publish/chat/message.ts +++ b/js/publish/src/chat/message.ts @@ -1,6 +1,6 @@ import type * as Moq from "@moq/lite"; import { Effect, Signal } from "@moq/signals"; -import * as Catalog from "../../catalog"; +import * as Catalog from "@moq/hang/catalog"; export type MessageProps = { enabled?: boolean | Signal; diff --git a/js/hang/src/publish/chat/typing.ts b/js/publish/src/chat/typing.ts similarity index 95% rename from js/hang/src/publish/chat/typing.ts rename to js/publish/src/chat/typing.ts index d6de88ef9..d90002326 100644 --- a/js/hang/src/publish/chat/typing.ts +++ b/js/publish/src/chat/typing.ts @@ -1,6 +1,6 @@ import type * as Moq from "@moq/lite"; import { Effect, Signal } from "@moq/signals"; -import * as Catalog from "../../catalog"; +import * as Catalog from "@moq/hang/catalog"; export type TypingProps = { enabled?: boolean | Signal; diff --git a/js/hang/src/publish/element.ts b/js/publish/src/element.ts similarity index 100% rename from js/hang/src/publish/element.ts rename to js/publish/src/element.ts diff --git a/js/hang/src/publish/index.ts b/js/publish/src/index.ts similarity index 82% rename from js/hang/src/publish/index.ts rename to js/publish/src/index.ts index 79ccab233..52ebdbf89 100644 --- a/js/hang/src/publish/index.ts +++ b/js/publish/src/index.ts @@ -8,4 +8,4 @@ export * as User from "./user"; export * as Video from "./video"; // NOTE: element is not exported from this module -// You have to import it from @moq/hang/publish/element instead. +// You have to import it from @moq/publish/element instead. diff --git a/js/hang/src/publish/location/index.ts b/js/publish/src/location/index.ts similarity index 94% rename from js/hang/src/publish/location/index.ts rename to js/publish/src/location/index.ts index 1c4cb3136..09e34a4ac 100644 --- a/js/hang/src/publish/location/index.ts +++ b/js/publish/src/location/index.ts @@ -1,5 +1,5 @@ import { Effect, Signal } from "@moq/signals"; -import type { Catalog } from "../.."; +import type * as Catalog from "@moq/hang/catalog"; import { Peers, type PeersProps } from "./peers"; import { Window, type WindowProps } from "./window"; diff --git a/js/hang/src/publish/location/peers.ts b/js/publish/src/location/peers.ts similarity index 96% rename from js/hang/src/publish/location/peers.ts rename to js/publish/src/location/peers.ts index 2a0222fe0..9c9c43638 100644 --- a/js/hang/src/publish/location/peers.ts +++ b/js/publish/src/location/peers.ts @@ -1,7 +1,7 @@ import type * as Moq from "@moq/lite"; import * as Zod from "@moq/lite/zod"; import { Effect, Signal } from "@moq/signals"; -import * as Catalog from "../../catalog"; +import * as Catalog from "@moq/hang/catalog"; export interface PeersProps { enabled?: boolean | Signal; diff --git a/js/hang/src/publish/location/window.ts b/js/publish/src/location/window.ts similarity index 97% rename from js/hang/src/publish/location/window.ts rename to js/publish/src/location/window.ts index e7a2d3529..1a885ec2e 100644 --- a/js/hang/src/publish/location/window.ts +++ b/js/publish/src/location/window.ts @@ -1,7 +1,7 @@ import type * as Moq from "@moq/lite"; import * as Zod from "@moq/lite/zod"; import { Effect, Signal } from "@moq/signals"; -import * as Catalog from "../../catalog"; +import * as Catalog from "@moq/hang/catalog"; export type WindowProps = { // If true, then we'll publish our position to the broadcast. diff --git a/js/hang/src/publish/preview.ts b/js/publish/src/preview.ts similarity index 95% rename from js/hang/src/publish/preview.ts rename to js/publish/src/preview.ts index 375e60994..fc212243c 100644 --- a/js/hang/src/publish/preview.ts +++ b/js/publish/src/preview.ts @@ -1,6 +1,6 @@ import type * as Moq from "@moq/lite"; import { Effect, Signal } from "@moq/signals"; -import * as Catalog from "../catalog"; +import * as Catalog from "@moq/hang/catalog"; export type PreviewProps = { enabled?: boolean | Signal; diff --git a/js/hang/src/publish/source/camera.ts b/js/publish/src/source/camera.ts similarity index 100% rename from js/hang/src/publish/source/camera.ts rename to js/publish/src/source/camera.ts diff --git a/js/hang/src/publish/source/device.ts b/js/publish/src/source/device.ts similarity index 100% rename from js/hang/src/publish/source/device.ts rename to js/publish/src/source/device.ts diff --git a/js/hang/src/publish/source/file.ts b/js/publish/src/source/file.ts similarity index 100% rename from js/hang/src/publish/source/file.ts rename to js/publish/src/source/file.ts diff --git a/js/hang/src/publish/source/index.ts b/js/publish/src/source/index.ts similarity index 100% rename from js/hang/src/publish/source/index.ts rename to js/publish/src/source/index.ts diff --git a/js/hang/src/publish/source/microphone.ts b/js/publish/src/source/microphone.ts similarity index 100% rename from js/hang/src/publish/source/microphone.ts rename to js/publish/src/source/microphone.ts diff --git a/js/hang/src/publish/source/screen.ts b/js/publish/src/source/screen.ts similarity index 100% rename from js/hang/src/publish/source/screen.ts rename to js/publish/src/source/screen.ts diff --git a/js/hang/src/publish/user.ts b/js/publish/src/user.ts similarity index 95% rename from js/hang/src/publish/user.ts rename to js/publish/src/user.ts index 15f46e577..941d48e7c 100644 --- a/js/hang/src/publish/user.ts +++ b/js/publish/src/user.ts @@ -1,5 +1,5 @@ import { Effect, Signal } from "@moq/signals"; -import type * as Catalog from "../catalog"; +import type * as Catalog from "@moq/hang/catalog"; export type Props = { enabled?: boolean | Signal; diff --git a/js/hang/src/publish/video/encoder.ts b/js/publish/src/video/encoder.ts similarity index 98% rename from js/hang/src/publish/video/encoder.ts rename to js/publish/src/video/encoder.ts index 5925ef52f..21dbc50fc 100644 --- a/js/hang/src/publish/video/encoder.ts +++ b/js/publish/src/video/encoder.ts @@ -1,9 +1,9 @@ import type * as Moq from "@moq/lite"; import { Time } from "@moq/lite"; import { Effect, type Getter, Signal } from "@moq/signals"; -import * as Catalog from "../../catalog"; -import * as Container from "../../container"; -import { isFirefox } from "../../util/hacks"; +import * as Catalog from "@moq/hang/catalog"; +import * as Container from "@moq/hang/container"; +import { isFirefox } from "@moq/hang/util/hacks"; import type { Source } from "./types"; export interface EncoderProps { diff --git a/js/hang/src/publish/video/index.ts b/js/publish/src/video/index.ts similarity index 98% rename from js/hang/src/publish/video/index.ts rename to js/publish/src/video/index.ts index 2f254a786..0950d9443 100644 --- a/js/hang/src/publish/video/index.ts +++ b/js/publish/src/video/index.ts @@ -1,5 +1,5 @@ import { Effect, Signal } from "@moq/signals"; -import * as Catalog from "../../catalog"; +import * as Catalog from "@moq/hang/catalog"; import { Encoder, type EncoderProps } from "./encoder"; import { TrackProcessor } from "./polyfill"; import type { Source } from "./types"; diff --git a/js/hang/src/publish/video/polyfill.ts b/js/publish/src/video/polyfill.ts similarity index 100% rename from js/hang/src/publish/video/polyfill.ts rename to js/publish/src/video/polyfill.ts diff --git a/js/hang/src/publish/video/types.ts b/js/publish/src/video/types.ts similarity index 100% rename from js/hang/src/publish/video/types.ts rename to js/publish/src/video/types.ts diff --git a/js/publish/src/worklet.d.ts b/js/publish/src/worklet.d.ts new file mode 100644 index 000000000..7bb79c31d --- /dev/null +++ b/js/publish/src/worklet.d.ts @@ -0,0 +1,4 @@ +declare module "*-worklet.ts?worker&url" { + const url: string; + export default url; +} diff --git a/js/publish/tsconfig.json b/js/publish/tsconfig.json new file mode 100644 index 000000000..8a2f570f1 --- /dev/null +++ b/js/publish/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "dist", + "composite": true + }, + "include": ["src"] +} diff --git a/package.json b/package.json index 0ffe1f714..4c67e76ff 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "js/hang", "js/ui-core", "js/watch", + "js/publish", "js/hang-ui", "js/hang-demo", "js/signals" From 924dbdd7ddac5ccc4cc156c7bd47196a72019a02 Mon Sep 17 00:00:00 2001 From: James Reetzke Date: Wed, 11 Feb 2026 23:04:00 -0500 Subject: [PATCH 04/17] refactor: move watch UI into @moq/watch/ui (Milestone 4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the watch UI web component from @moq/hang-ui into @moq/watch, co-locating the UI with the logic it wraps. The custom element is now available via @moq/watch/ui. Files changed: - js/watch/src/ui/ (new): 15 files moved from hang-ui/src/watch/ — context.tsx, element.tsx, index.tsx, hooks/use-watch-ui.ts, styles/index.css, and 10 components (BufferControl, BufferingIndicator, FullscreenButton, LatencySlider, PlayPauseButton, QualitySelector, StatsButton, VolumeSlider, WatchControls, WatchStatusIndicator). - js/watch/src/ui/{context,element,index}.tsx: Updated imports from @moq/watch/element to ../element and @moq/watch to .. (now local within the package). - js/watch/src/ui/components/BufferControl.tsx: Updated BufferedRange import from @moq/watch to ../.. (local). - js/watch/package.json: Added ./ui export pointing to src/ui/index.tsx; added src/ui/index.tsx to sideEffects; added @moq/ui-core dependency; added solid-element, solid-js, vite, vite-plugin-solid as dev deps; updated build script to run both tsc and vite. - js/watch/vite.config.ts (new): Vite lib mode config building the UI entry point with solid plugin, externalizing @moq/hang, @moq/lite, @moq/signals, @moq/ui-core. Uses emptyOutDir: false to preserve tsc output in dist/. - js/watch/tsconfig.json: Added exclude for src/ui since .tsx files are built by vite, not tsc. - js/watch/README.md: Added UI Web Component section documenting element and its dependency on @moq/ui-core. - js/hang-ui/src/watch/ (deleted): Moved entirely into @moq/watch/ui. - js/hang-ui/package.json: Removed ./watch export and sideEffect; removed @moq/watch peer dependency. - js/hang-ui/vite.config.ts: Removed watch entry point and @moq/watch from rollup externals. - js/hang-demo/src/index.ts: Updated watch UI import from @moq/hang-ui/watch to @moq/watch/ui. --- bun.lock | 62 ++++++++++++++++++- js/hang-demo/src/index.ts | 2 +- js/hang-ui/package.json | 7 +-- js/hang-ui/vite.config.ts | 3 +- js/watch/README.md | 19 ++++++ js/watch/package.json | 17 +++-- .../src/ui}/components/BufferControl.tsx | 2 +- .../src/ui}/components/BufferingIndicator.tsx | 0 .../src/ui}/components/FullscreenButton.tsx | 0 .../src/ui}/components/LatencySlider.tsx | 0 .../src/ui}/components/PlayPauseButton.tsx | 0 .../src/ui}/components/QualitySelector.tsx | 0 .../src/ui}/components/StatsButton.tsx | 0 .../src/ui}/components/VolumeSlider.tsx | 0 .../src/ui}/components/WatchControls.tsx | 0 .../ui}/components/WatchStatusIndicator.tsx | 0 .../src/watch => watch/src/ui}/context.tsx | 4 +- .../src/watch => watch/src/ui}/element.tsx | 2 +- .../src/ui}/hooks/use-watch-ui.ts | 0 .../src/watch => watch/src/ui}/index.tsx | 2 +- .../watch => watch/src/ui}/styles/index.css | 0 js/watch/tsconfig.json | 3 +- js/watch/vite.config.ts | 22 +++++++ 23 files changed, 125 insertions(+), 20 deletions(-) rename js/{hang-ui/src/watch => watch/src/ui}/components/BufferControl.tsx (99%) rename js/{hang-ui/src/watch => watch/src/ui}/components/BufferingIndicator.tsx (100%) rename js/{hang-ui/src/watch => watch/src/ui}/components/FullscreenButton.tsx (100%) rename js/{hang-ui/src/watch => watch/src/ui}/components/LatencySlider.tsx (100%) rename js/{hang-ui/src/watch => watch/src/ui}/components/PlayPauseButton.tsx (100%) rename js/{hang-ui/src/watch => watch/src/ui}/components/QualitySelector.tsx (100%) rename js/{hang-ui/src/watch => watch/src/ui}/components/StatsButton.tsx (100%) rename js/{hang-ui/src/watch => watch/src/ui}/components/VolumeSlider.tsx (100%) rename js/{hang-ui/src/watch => watch/src/ui}/components/WatchControls.tsx (100%) rename js/{hang-ui/src/watch => watch/src/ui}/components/WatchStatusIndicator.tsx (100%) rename js/{hang-ui/src/watch => watch/src/ui}/context.tsx (98%) rename js/{hang-ui/src/watch => watch/src/ui}/element.tsx (95%) rename js/{hang-ui/src/watch => watch/src/ui}/hooks/use-watch-ui.ts (100%) rename js/{hang-ui/src/watch => watch/src/ui}/index.tsx (93%) rename js/{hang-ui/src/watch => watch/src/ui}/styles/index.css (100%) create mode 100644 js/watch/vite.config.ts diff --git a/bun.lock b/bun.lock index 2cffebc28..0bf8928a3 100644 --- a/bun.lock +++ b/bun.lock @@ -93,7 +93,6 @@ "@moq/publish": "workspace:^0.1.0", "@moq/signals": "workspace:^0.1.0", "@moq/ui-core": "workspace:^0.1.0", - "@moq/watch": "workspace:^0.1.0", }, }, "js/lite": { @@ -196,12 +195,17 @@ "@moq/hang": "workspace:^", "@moq/lite": "workspace:^", "@moq/signals": "workspace:^", + "@moq/ui-core": "workspace:^", }, "devDependencies": { "@types/audioworklet": "^0.0.77", "@typescript/lib-dom": "npm:@types/web@^0.0.241", "rimraf": "^6.0.1", + "solid-element": "^1.9.1", + "solid-js": "^1.9.10", "typescript": "^5.9.2", + "vite": "^7.3.1", + "vite-plugin-solid": "^2.11.10", }, }, }, @@ -1481,6 +1485,8 @@ "@moq/ui-core/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], + "@moq/watch/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], + "@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], @@ -1563,6 +1569,8 @@ "@moq/ui-core/vite/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], + "@moq/watch/vite/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], + "module-lookup-amd/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], "unplugin-solid/merge-anything/is-what": ["is-what@5.5.0", "", {}, "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw=="], @@ -1727,6 +1735,58 @@ "@moq/ui-core/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], + "@moq/watch/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], + + "@moq/watch/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="], + + "@moq/watch/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.2", "", { "os": "android", "cpu": "arm64" }, "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA=="], + + "@moq/watch/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.2", "", { "os": "android", "cpu": "x64" }, "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A=="], + + "@moq/watch/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg=="], + + "@moq/watch/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA=="], + + "@moq/watch/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g=="], + + "@moq/watch/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA=="], + + "@moq/watch/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.2", "", { "os": "linux", "cpu": "arm" }, "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw=="], + + "@moq/watch/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw=="], + + "@moq/watch/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w=="], + + "@moq/watch/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg=="], + + "@moq/watch/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw=="], + + "@moq/watch/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ=="], + + "@moq/watch/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA=="], + + "@moq/watch/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w=="], + + "@moq/watch/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA=="], + + "@moq/watch/vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw=="], + + "@moq/watch/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.2", "", { "os": "none", "cpu": "x64" }, "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA=="], + + "@moq/watch/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA=="], + + "@moq/watch/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg=="], + + "@moq/watch/vite/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag=="], + + "@moq/watch/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg=="], + + "@moq/watch/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg=="], + + "@moq/watch/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ=="], + + "@moq/watch/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], + "module-lookup-amd/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "vitepress/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="], diff --git a/js/hang-demo/src/index.ts b/js/hang-demo/src/index.ts index fe5bacf47..c3a6390ce 100644 --- a/js/hang-demo/src/index.ts +++ b/js/hang-demo/src/index.ts @@ -1,5 +1,5 @@ import "./highlight"; -import "@moq/hang-ui/watch"; +import "@moq/watch/ui"; import HangSupport from "@moq/hang/support/element"; import HangWatch from "@moq/watch/element"; import HangConfig from "./config"; diff --git a/js/hang-ui/package.json b/js/hang-ui/package.json index f79353687..10f3aed17 100644 --- a/js/hang-ui/package.json +++ b/js/hang-ui/package.json @@ -6,12 +6,10 @@ "license": "(MIT OR Apache-2.0)", "repository": "github:moq-dev/moq", "exports": { - "./publish": "./src/publish/index.tsx", - "./watch": "./src/watch/index.tsx" + "./publish": "./src/publish/index.tsx" }, "sideEffects": [ - "./src/publish/index.tsx", - "./src/watch/index.tsx" + "./src/publish/index.tsx" ], "scripts": { "build": "bun run clean && vite build && bun ../scripts/package.ts", @@ -24,7 +22,6 @@ "@moq/hang": "workspace:^0.1.0", "@moq/signals": "workspace:^0.1.0", "@moq/ui-core": "workspace:^0.1.0", - "@moq/watch": "workspace:^0.1.0", "@moq/publish": "workspace:^0.1.0" }, "devDependencies": { diff --git a/js/hang-ui/vite.config.ts b/js/hang-ui/vite.config.ts index e7d6dd206..cea05dd83 100644 --- a/js/hang-ui/vite.config.ts +++ b/js/hang-ui/vite.config.ts @@ -8,12 +8,11 @@ export default defineConfig({ lib: { entry: { "publish/index": resolve(__dirname, "src/publish/index.tsx"), - "watch/index": resolve(__dirname, "src/watch/index.tsx"), }, formats: ["es"], }, rollupOptions: { - external: ["@moq/hang", "@moq/lite", "@moq/signals", "@moq/ui-core", "@moq/watch", "@moq/publish"], + external: ["@moq/hang", "@moq/lite", "@moq/signals", "@moq/ui-core", "@moq/publish"], }, sourcemap: true, target: "esnext", diff --git a/js/watch/README.md b/js/watch/README.md index 92e12ceb7..64d1ddec8 100644 --- a/js/watch/README.md +++ b/js/watch/README.md @@ -66,6 +66,25 @@ watch.video.media.subscribe((stream) => { }); ``` +## UI Web Component + +`@moq/watch` includes a SolidJS-powered UI overlay (``) with playback controls, volume, buffering indicator, quality selector, and stats panel. It depends on [`@moq/ui-core`](../ui-core) for shared UI primitives. + +```html + + + + + + + +``` + +The `` element automatically discovers the nested `` element and wires up reactive controls. + ## Features - **WebCodecs decoding** — Hardware-accelerated video and audio decoding diff --git a/js/watch/package.json b/js/watch/package.json index d1b8d7791..3b41cac5f 100644 --- a/js/watch/package.json +++ b/js/watch/package.json @@ -7,25 +7,32 @@ "repository": "github:moq-dev/moq", "exports": { ".": "./src/index.ts", - "./element": "./src/element.ts" + "./element": "./src/element.ts", + "./ui": "./src/ui/index.tsx" }, "sideEffects": [ - "./src/element.ts" + "./src/element.ts", + "./src/ui/index.tsx" ], "scripts": { - "build": "rimraf dist && tsc -b && bun ../scripts/package.ts", + "build": "rimraf dist && tsc -b && vite build && bun ../scripts/package.ts", "check": "tsc --noEmit", "release": "bun ../scripts/release.ts" }, "dependencies": { "@moq/hang": "workspace:^", "@moq/lite": "workspace:^", - "@moq/signals": "workspace:^" + "@moq/signals": "workspace:^", + "@moq/ui-core": "workspace:^" }, "devDependencies": { "@types/audioworklet": "^0.0.77", "@typescript/lib-dom": "npm:@types/web@^0.0.241", "rimraf": "^6.0.1", - "typescript": "^5.9.2" + "solid-element": "^1.9.1", + "solid-js": "^1.9.10", + "typescript": "^5.9.2", + "vite": "^7.3.1", + "vite-plugin-solid": "^2.11.10" } } diff --git a/js/hang-ui/src/watch/components/BufferControl.tsx b/js/watch/src/ui/components/BufferControl.tsx similarity index 99% rename from js/hang-ui/src/watch/components/BufferControl.tsx rename to js/watch/src/ui/components/BufferControl.tsx index 27362cf01..3e27c7e48 100644 --- a/js/hang-ui/src/watch/components/BufferControl.tsx +++ b/js/watch/src/ui/components/BufferControl.tsx @@ -1,5 +1,5 @@ import { Moq } from "@moq/hang"; -import type { BufferedRange } from "@moq/watch"; +import type { BufferedRange } from "../.."; import { createMemo, createSignal, For, onCleanup, Show } from "solid-js"; import useWatchUIContext from "../hooks/use-watch-ui"; diff --git a/js/hang-ui/src/watch/components/BufferingIndicator.tsx b/js/watch/src/ui/components/BufferingIndicator.tsx similarity index 100% rename from js/hang-ui/src/watch/components/BufferingIndicator.tsx rename to js/watch/src/ui/components/BufferingIndicator.tsx diff --git a/js/hang-ui/src/watch/components/FullscreenButton.tsx b/js/watch/src/ui/components/FullscreenButton.tsx similarity index 100% rename from js/hang-ui/src/watch/components/FullscreenButton.tsx rename to js/watch/src/ui/components/FullscreenButton.tsx diff --git a/js/hang-ui/src/watch/components/LatencySlider.tsx b/js/watch/src/ui/components/LatencySlider.tsx similarity index 100% rename from js/hang-ui/src/watch/components/LatencySlider.tsx rename to js/watch/src/ui/components/LatencySlider.tsx diff --git a/js/hang-ui/src/watch/components/PlayPauseButton.tsx b/js/watch/src/ui/components/PlayPauseButton.tsx similarity index 100% rename from js/hang-ui/src/watch/components/PlayPauseButton.tsx rename to js/watch/src/ui/components/PlayPauseButton.tsx diff --git a/js/hang-ui/src/watch/components/QualitySelector.tsx b/js/watch/src/ui/components/QualitySelector.tsx similarity index 100% rename from js/hang-ui/src/watch/components/QualitySelector.tsx rename to js/watch/src/ui/components/QualitySelector.tsx diff --git a/js/hang-ui/src/watch/components/StatsButton.tsx b/js/watch/src/ui/components/StatsButton.tsx similarity index 100% rename from js/hang-ui/src/watch/components/StatsButton.tsx rename to js/watch/src/ui/components/StatsButton.tsx diff --git a/js/hang-ui/src/watch/components/VolumeSlider.tsx b/js/watch/src/ui/components/VolumeSlider.tsx similarity index 100% rename from js/hang-ui/src/watch/components/VolumeSlider.tsx rename to js/watch/src/ui/components/VolumeSlider.tsx diff --git a/js/hang-ui/src/watch/components/WatchControls.tsx b/js/watch/src/ui/components/WatchControls.tsx similarity index 100% rename from js/hang-ui/src/watch/components/WatchControls.tsx rename to js/watch/src/ui/components/WatchControls.tsx diff --git a/js/hang-ui/src/watch/components/WatchStatusIndicator.tsx b/js/watch/src/ui/components/WatchStatusIndicator.tsx similarity index 100% rename from js/hang-ui/src/watch/components/WatchStatusIndicator.tsx rename to js/watch/src/ui/components/WatchStatusIndicator.tsx diff --git a/js/hang-ui/src/watch/context.tsx b/js/watch/src/ui/context.tsx similarity index 98% rename from js/hang-ui/src/watch/context.tsx rename to js/watch/src/ui/context.tsx index 6fa854021..d07657a88 100644 --- a/js/hang-ui/src/watch/context.tsx +++ b/js/watch/src/ui/context.tsx @@ -1,6 +1,6 @@ import { type Moq, Signals } from "@moq/hang"; -import type { BufferedRanges } from "@moq/watch"; -import type HangWatch from "@moq/watch/element"; +import type { BufferedRanges } from ".."; +import type HangWatch from "../element"; import solid from "@moq/signals/solid"; import type { JSX } from "solid-js"; import { createContext, createSignal, onCleanup } from "solid-js"; diff --git a/js/hang-ui/src/watch/element.tsx b/js/watch/src/ui/element.tsx similarity index 95% rename from js/hang-ui/src/watch/element.tsx rename to js/watch/src/ui/element.tsx index 33abd437d..18635765d 100644 --- a/js/hang-ui/src/watch/element.tsx +++ b/js/watch/src/ui/element.tsx @@ -1,4 +1,4 @@ -import type HangWatch from "@moq/watch/element"; +import type HangWatch from "../element"; import { useContext } from "solid-js"; import { Show } from "solid-js/web"; import { Stats } from "@moq/ui-core"; diff --git a/js/hang-ui/src/watch/hooks/use-watch-ui.ts b/js/watch/src/ui/hooks/use-watch-ui.ts similarity index 100% rename from js/hang-ui/src/watch/hooks/use-watch-ui.ts rename to js/watch/src/ui/hooks/use-watch-ui.ts diff --git a/js/hang-ui/src/watch/index.tsx b/js/watch/src/ui/index.tsx similarity index 93% rename from js/hang-ui/src/watch/index.tsx rename to js/watch/src/ui/index.tsx index 950dbfa16..acffbd251 100644 --- a/js/hang-ui/src/watch/index.tsx +++ b/js/watch/src/ui/index.tsx @@ -1,4 +1,4 @@ -import type HangWatch from "@moq/watch/element"; +import type HangWatch from "../element"; import { customElement } from "solid-element"; import { createSignal, onMount, Show } from "solid-js"; import { WatchUI } from "./element.tsx"; diff --git a/js/hang-ui/src/watch/styles/index.css b/js/watch/src/ui/styles/index.css similarity index 100% rename from js/hang-ui/src/watch/styles/index.css rename to js/watch/src/ui/styles/index.css diff --git a/js/watch/tsconfig.json b/js/watch/tsconfig.json index 8a2f570f1..7089f27a3 100644 --- a/js/watch/tsconfig.json +++ b/js/watch/tsconfig.json @@ -5,5 +5,6 @@ "outDir": "dist", "composite": true }, - "include": ["src"] + "include": ["src"], + "exclude": ["src/ui"] } diff --git a/js/watch/vite.config.ts b/js/watch/vite.config.ts new file mode 100644 index 000000000..eea75c27c --- /dev/null +++ b/js/watch/vite.config.ts @@ -0,0 +1,22 @@ +import { resolve } from "path"; +import { defineConfig } from "vite"; +import solidPlugin from "vite-plugin-solid"; + +export default defineConfig({ + plugins: [solidPlugin()], + build: { + lib: { + entry: { + "ui/index": resolve(__dirname, "src/ui/index.tsx"), + }, + formats: ["es"], + }, + rollupOptions: { + external: ["@moq/hang", "@moq/lite", "@moq/signals", "@moq/ui-core"], + }, + outDir: "dist", + emptyOutDir: false, + sourcemap: true, + target: "esnext", + }, +}); From 4ea5e2021127d4040d29f95b488db1a66b938c8d Mon Sep 17 00:00:00 2001 From: James Reetzke Date: Wed, 11 Feb 2026 23:23:30 -0500 Subject: [PATCH 05/17] refactor: move publish UI into @moq/publish/ui (Milestone 5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the publish UI web component from @moq/hang-ui into @moq/publish, co-locating the UI with the logic it wraps. The custom element is now available via @moq/publish/ui. Files changed: - js/publish/src/ui/ (new): 17 files moved from hang-ui/src/publish/ — context.tsx, element.tsx, index.tsx, hooks/use-publish-ui.ts, styles/{index,controls,media-selector,source-button,status-indicator}.css, and 8 components (CameraSourceButton, FileSourceButton, MediaSourceSelector, MicrophoneSourceButton, NothingSourceButton, PublishControls, PublishStatusIndicator, ScreenSourceButton). - js/publish/src/ui/{context,element,index}.tsx: Updated imports from @moq/publish/element to ../element (now local within the package). - js/publish/package.json: Added ./ui export pointing to src/ui/index.tsx; added src/ui/index.tsx to sideEffects; added @moq/ui-core dependency; added solid-element, solid-js, vite, vite-plugin-solid as dev deps; updated build script to run both tsc and vite. - js/publish/vite.config.ts (new): Vite lib mode config building the UI entry point with solid plugin, externalizing @moq/hang, @moq/lite, @moq/signals, @moq/ui-core. Uses emptyOutDir: false to preserve tsc output in dist/. - js/publish/tsconfig.json: Added exclude for src/ui since .tsx files are built by vite, not tsc. - js/publish/README.md: Added UI Web Component section documenting element and its dependency on @moq/ui-core. - js/hang-ui/src/publish/ (deleted): Moved entirely into @moq/publish/ui. - js/hang-ui/package.json: Removed ./publish export, sideEffect, and all peer dependencies (package is now empty). - js/hang-ui/vite.config.ts: Removed publish entry point (entry is now empty object). - js/hang-demo/src/publish.ts: Updated publish UI import from @moq/hang-ui/publish to @moq/publish/ui. --- bun.lock | 67 +++++++++++++++++-- js/hang-demo/src/publish.ts | 2 +- js/hang-ui/package.json | 15 +---- js/hang-ui/vite.config.ts | 4 +- js/publish/README.md | 19 ++++++ js/publish/package.json | 17 +++-- .../src/ui}/components/CameraSourceButton.tsx | 0 .../src/ui}/components/FileSourceButton.tsx | 0 .../ui}/components/MediaSourceSelector.tsx | 0 .../ui}/components/MicrophoneSourceButton.tsx | 0 .../ui}/components/NothingSourceButton.tsx | 0 .../src/ui}/components/PublishControls.tsx | 0 .../ui}/components/PublishStatusIndicator.tsx | 0 .../src/ui}/components/ScreenSourceButton.tsx | 0 .../publish => publish/src/ui}/context.tsx | 2 +- .../publish => publish/src/ui}/element.tsx | 2 +- .../src/ui}/hooks/use-publish-ui.ts | 0 .../src/publish => publish/src/ui}/index.tsx | 2 +- .../src/ui}/styles/controls.css | 0 .../src/ui}/styles/index.css | 0 .../src/ui}/styles/media-selector.css | 0 .../src/ui}/styles/source-button.css | 0 .../src/ui}/styles/status-indicator.css | 0 js/publish/tsconfig.json | 3 +- js/publish/vite.config.ts | 22 ++++++ 25 files changed, 124 insertions(+), 31 deletions(-) rename js/{hang-ui/src/publish => publish/src/ui}/components/CameraSourceButton.tsx (100%) rename js/{hang-ui/src/publish => publish/src/ui}/components/FileSourceButton.tsx (100%) rename js/{hang-ui/src/publish => publish/src/ui}/components/MediaSourceSelector.tsx (100%) rename js/{hang-ui/src/publish => publish/src/ui}/components/MicrophoneSourceButton.tsx (100%) rename js/{hang-ui/src/publish => publish/src/ui}/components/NothingSourceButton.tsx (100%) rename js/{hang-ui/src/publish => publish/src/ui}/components/PublishControls.tsx (100%) rename js/{hang-ui/src/publish => publish/src/ui}/components/PublishStatusIndicator.tsx (100%) rename js/{hang-ui/src/publish => publish/src/ui}/components/ScreenSourceButton.tsx (100%) rename js/{hang-ui/src/publish => publish/src/ui}/context.tsx (99%) rename js/{hang-ui/src/publish => publish/src/ui}/element.tsx (88%) rename js/{hang-ui/src/publish => publish/src/ui}/hooks/use-publish-ui.ts (100%) rename js/{hang-ui/src/publish => publish/src/ui}/index.tsx (93%) rename js/{hang-ui/src/publish => publish/src/ui}/styles/controls.css (100%) rename js/{hang-ui/src/publish => publish/src/ui}/styles/index.css (100%) rename js/{hang-ui/src/publish => publish/src/ui}/styles/media-selector.css (100%) rename js/{hang-ui/src/publish => publish/src/ui}/styles/source-button.css (100%) rename js/{hang-ui/src/publish => publish/src/ui}/styles/status-indicator.css (100%) create mode 100644 js/publish/vite.config.ts diff --git a/bun.lock b/bun.lock index 0bf8928a3..e5ab9013a 100644 --- a/bun.lock +++ b/bun.lock @@ -88,12 +88,6 @@ "vite": "^7.3.1", "vite-plugin-solid": "^2.11.10", }, - "peerDependencies": { - "@moq/hang": "workspace:^0.1.0", - "@moq/publish": "workspace:^0.1.0", - "@moq/signals": "workspace:^0.1.0", - "@moq/ui-core": "workspace:^0.1.0", - }, }, "js/lite": { "name": "@moq/lite", @@ -123,12 +117,17 @@ "@moq/hang": "workspace:^", "@moq/lite": "workspace:^", "@moq/signals": "workspace:^", + "@moq/ui-core": "workspace:^", }, "devDependencies": { "@types/audioworklet": "^0.0.77", "@typescript/lib-dom": "npm:@types/web@^0.0.241", "rimraf": "^6.0.1", + "solid-element": "^1.9.1", + "solid-js": "^1.9.10", "typescript": "^5.9.2", + "vite": "^7.3.1", + "vite-plugin-solid": "^2.11.10", }, }, "js/signals": { @@ -1483,6 +1482,8 @@ "@moq/hang-ui/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], + "@moq/publish/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], + "@moq/ui-core/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], "@moq/watch/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], @@ -1567,6 +1568,8 @@ "@moq/hang-ui/vite/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], + "@moq/publish/vite/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], + "@moq/ui-core/vite/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], "@moq/watch/vite/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], @@ -1683,6 +1686,58 @@ "@moq/hang-ui/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], + "@moq/publish/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], + + "@moq/publish/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="], + + "@moq/publish/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.2", "", { "os": "android", "cpu": "arm64" }, "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA=="], + + "@moq/publish/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.2", "", { "os": "android", "cpu": "x64" }, "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A=="], + + "@moq/publish/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg=="], + + "@moq/publish/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA=="], + + "@moq/publish/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g=="], + + "@moq/publish/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA=="], + + "@moq/publish/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.2", "", { "os": "linux", "cpu": "arm" }, "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw=="], + + "@moq/publish/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw=="], + + "@moq/publish/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w=="], + + "@moq/publish/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg=="], + + "@moq/publish/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw=="], + + "@moq/publish/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ=="], + + "@moq/publish/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA=="], + + "@moq/publish/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w=="], + + "@moq/publish/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA=="], + + "@moq/publish/vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw=="], + + "@moq/publish/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.2", "", { "os": "none", "cpu": "x64" }, "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA=="], + + "@moq/publish/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA=="], + + "@moq/publish/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg=="], + + "@moq/publish/vite/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag=="], + + "@moq/publish/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg=="], + + "@moq/publish/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg=="], + + "@moq/publish/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ=="], + + "@moq/publish/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], + "@moq/ui-core/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], "@moq/ui-core/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="], diff --git a/js/hang-demo/src/publish.ts b/js/hang-demo/src/publish.ts index 5532a27c6..a6c2eaddc 100644 --- a/js/hang-demo/src/publish.ts +++ b/js/hang-demo/src/publish.ts @@ -1,5 +1,5 @@ import "./highlight"; -import "@moq/hang-ui/publish"; +import "@moq/publish/ui"; // We need to import Web Components with fully-qualified paths because of tree-shaking. import HangPublish from "@moq/publish/element"; diff --git a/js/hang-ui/package.json b/js/hang-ui/package.json index 10f3aed17..b01cb5c3a 100644 --- a/js/hang-ui/package.json +++ b/js/hang-ui/package.json @@ -5,12 +5,8 @@ "description": "Media over QUIC library UI components", "license": "(MIT OR Apache-2.0)", "repository": "github:moq-dev/moq", - "exports": { - "./publish": "./src/publish/index.tsx" - }, - "sideEffects": [ - "./src/publish/index.tsx" - ], + "exports": {}, + "sideEffects": [], "scripts": { "build": "bun run clean && vite build && bun ../scripts/package.ts", "check": "tsc --noEmit", @@ -18,12 +14,7 @@ "fix": "biome check src --fix", "release": "bun ../scripts/release.ts" }, - "peerDependencies": { - "@moq/hang": "workspace:^0.1.0", - "@moq/signals": "workspace:^0.1.0", - "@moq/ui-core": "workspace:^0.1.0", - "@moq/publish": "workspace:^0.1.0" - }, + "peerDependencies": {}, "devDependencies": { "@types/audioworklet": "^0.0.77", "@typescript/lib-dom": "npm:@types/web@^0.0.241", diff --git a/js/hang-ui/vite.config.ts b/js/hang-ui/vite.config.ts index cea05dd83..2050b31e5 100644 --- a/js/hang-ui/vite.config.ts +++ b/js/hang-ui/vite.config.ts @@ -6,9 +6,7 @@ export default defineConfig({ plugins: [solidPlugin()], build: { lib: { - entry: { - "publish/index": resolve(__dirname, "src/publish/index.tsx"), - }, + entry: {}, formats: ["es"], }, rollupOptions: { diff --git a/js/publish/README.md b/js/publish/README.md index 03d6fd5e4..7135b0ad9 100644 --- a/js/publish/README.md +++ b/js/publish/README.md @@ -63,6 +63,25 @@ const publish = new Publish.Broadcast(connection, { publish.source.camera.enabled.set(true); ``` +## UI Web Component + +`@moq/publish` includes a SolidJS-powered UI overlay (``) with source selection (camera, screen, file, microphone) and status indicator. It depends on [`@moq/ui-core`](../ui-core) for shared UI primitives. + +```html + + + + + + + +``` + +The `` element automatically discovers the nested `` element and wires up reactive controls. + ## Features - **Camera & microphone** — Capture from user devices diff --git a/js/publish/package.json b/js/publish/package.json index 46120e40b..126b1eace 100644 --- a/js/publish/package.json +++ b/js/publish/package.json @@ -7,25 +7,32 @@ "repository": "github:moq-dev/moq", "exports": { ".": "./src/index.ts", - "./element": "./src/element.ts" + "./element": "./src/element.ts", + "./ui": "./src/ui/index.tsx" }, "sideEffects": [ - "./src/element.ts" + "./src/element.ts", + "./src/ui/index.tsx" ], "scripts": { - "build": "rimraf dist && tsc -b && bun ../scripts/package.ts", + "build": "rimraf dist && tsc -b && vite build && bun ../scripts/package.ts", "check": "tsc --noEmit", "release": "bun ../scripts/release.ts" }, "dependencies": { "@moq/hang": "workspace:^", "@moq/lite": "workspace:^", - "@moq/signals": "workspace:^" + "@moq/signals": "workspace:^", + "@moq/ui-core": "workspace:^" }, "devDependencies": { "@types/audioworklet": "^0.0.77", "@typescript/lib-dom": "npm:@types/web@^0.0.241", "rimraf": "^6.0.1", - "typescript": "^5.9.2" + "solid-element": "^1.9.1", + "solid-js": "^1.9.10", + "typescript": "^5.9.2", + "vite": "^7.3.1", + "vite-plugin-solid": "^2.11.10" } } diff --git a/js/hang-ui/src/publish/components/CameraSourceButton.tsx b/js/publish/src/ui/components/CameraSourceButton.tsx similarity index 100% rename from js/hang-ui/src/publish/components/CameraSourceButton.tsx rename to js/publish/src/ui/components/CameraSourceButton.tsx diff --git a/js/hang-ui/src/publish/components/FileSourceButton.tsx b/js/publish/src/ui/components/FileSourceButton.tsx similarity index 100% rename from js/hang-ui/src/publish/components/FileSourceButton.tsx rename to js/publish/src/ui/components/FileSourceButton.tsx diff --git a/js/hang-ui/src/publish/components/MediaSourceSelector.tsx b/js/publish/src/ui/components/MediaSourceSelector.tsx similarity index 100% rename from js/hang-ui/src/publish/components/MediaSourceSelector.tsx rename to js/publish/src/ui/components/MediaSourceSelector.tsx diff --git a/js/hang-ui/src/publish/components/MicrophoneSourceButton.tsx b/js/publish/src/ui/components/MicrophoneSourceButton.tsx similarity index 100% rename from js/hang-ui/src/publish/components/MicrophoneSourceButton.tsx rename to js/publish/src/ui/components/MicrophoneSourceButton.tsx diff --git a/js/hang-ui/src/publish/components/NothingSourceButton.tsx b/js/publish/src/ui/components/NothingSourceButton.tsx similarity index 100% rename from js/hang-ui/src/publish/components/NothingSourceButton.tsx rename to js/publish/src/ui/components/NothingSourceButton.tsx diff --git a/js/hang-ui/src/publish/components/PublishControls.tsx b/js/publish/src/ui/components/PublishControls.tsx similarity index 100% rename from js/hang-ui/src/publish/components/PublishControls.tsx rename to js/publish/src/ui/components/PublishControls.tsx diff --git a/js/hang-ui/src/publish/components/PublishStatusIndicator.tsx b/js/publish/src/ui/components/PublishStatusIndicator.tsx similarity index 100% rename from js/hang-ui/src/publish/components/PublishStatusIndicator.tsx rename to js/publish/src/ui/components/PublishStatusIndicator.tsx diff --git a/js/hang-ui/src/publish/components/ScreenSourceButton.tsx b/js/publish/src/ui/components/ScreenSourceButton.tsx similarity index 100% rename from js/hang-ui/src/publish/components/ScreenSourceButton.tsx rename to js/publish/src/ui/components/ScreenSourceButton.tsx diff --git a/js/hang-ui/src/publish/context.tsx b/js/publish/src/ui/context.tsx similarity index 99% rename from js/hang-ui/src/publish/context.tsx rename to js/publish/src/ui/context.tsx index 4d0f23f02..36ba174ca 100644 --- a/js/hang-ui/src/publish/context.tsx +++ b/js/publish/src/ui/context.tsx @@ -1,4 +1,4 @@ -import type HangPublish from "@moq/publish/element"; +import type HangPublish from "../element"; import type { JSX } from "solid-js"; import { createContext, createEffect, createSignal } from "solid-js"; diff --git a/js/hang-ui/src/publish/element.tsx b/js/publish/src/ui/element.tsx similarity index 88% rename from js/hang-ui/src/publish/element.tsx rename to js/publish/src/ui/element.tsx index 915ba63f7..f21c7ef93 100644 --- a/js/hang-ui/src/publish/element.tsx +++ b/js/publish/src/ui/element.tsx @@ -1,4 +1,4 @@ -import type HangPublish from "@moq/publish/element"; +import type HangPublish from "../element"; import PublishControls from "./components/PublishControls"; import PublishControlsContextProvider from "./context"; import styles from "./styles/index.css?inline"; diff --git a/js/hang-ui/src/publish/hooks/use-publish-ui.ts b/js/publish/src/ui/hooks/use-publish-ui.ts similarity index 100% rename from js/hang-ui/src/publish/hooks/use-publish-ui.ts rename to js/publish/src/ui/hooks/use-publish-ui.ts diff --git a/js/hang-ui/src/publish/index.tsx b/js/publish/src/ui/index.tsx similarity index 93% rename from js/hang-ui/src/publish/index.tsx rename to js/publish/src/ui/index.tsx index 4856300d8..54ba315dc 100644 --- a/js/hang-ui/src/publish/index.tsx +++ b/js/publish/src/ui/index.tsx @@ -1,4 +1,4 @@ -import type HangPublish from "@moq/publish/element"; +import type HangPublish from "../element"; import { customElement } from "solid-element"; import { createSignal, onMount } from "solid-js"; import { Show } from "solid-js/web"; diff --git a/js/hang-ui/src/publish/styles/controls.css b/js/publish/src/ui/styles/controls.css similarity index 100% rename from js/hang-ui/src/publish/styles/controls.css rename to js/publish/src/ui/styles/controls.css diff --git a/js/hang-ui/src/publish/styles/index.css b/js/publish/src/ui/styles/index.css similarity index 100% rename from js/hang-ui/src/publish/styles/index.css rename to js/publish/src/ui/styles/index.css diff --git a/js/hang-ui/src/publish/styles/media-selector.css b/js/publish/src/ui/styles/media-selector.css similarity index 100% rename from js/hang-ui/src/publish/styles/media-selector.css rename to js/publish/src/ui/styles/media-selector.css diff --git a/js/hang-ui/src/publish/styles/source-button.css b/js/publish/src/ui/styles/source-button.css similarity index 100% rename from js/hang-ui/src/publish/styles/source-button.css rename to js/publish/src/ui/styles/source-button.css diff --git a/js/hang-ui/src/publish/styles/status-indicator.css b/js/publish/src/ui/styles/status-indicator.css similarity index 100% rename from js/hang-ui/src/publish/styles/status-indicator.css rename to js/publish/src/ui/styles/status-indicator.css diff --git a/js/publish/tsconfig.json b/js/publish/tsconfig.json index 8a2f570f1..7089f27a3 100644 --- a/js/publish/tsconfig.json +++ b/js/publish/tsconfig.json @@ -5,5 +5,6 @@ "outDir": "dist", "composite": true }, - "include": ["src"] + "include": ["src"], + "exclude": ["src/ui"] } diff --git a/js/publish/vite.config.ts b/js/publish/vite.config.ts new file mode 100644 index 000000000..eea75c27c --- /dev/null +++ b/js/publish/vite.config.ts @@ -0,0 +1,22 @@ +import { resolve } from "path"; +import { defineConfig } from "vite"; +import solidPlugin from "vite-plugin-solid"; + +export default defineConfig({ + plugins: [solidPlugin()], + build: { + lib: { + entry: { + "ui/index": resolve(__dirname, "src/ui/index.tsx"), + }, + formats: ["es"], + }, + rollupOptions: { + external: ["@moq/hang", "@moq/lite", "@moq/signals", "@moq/ui-core"], + }, + outDir: "dist", + emptyOutDir: false, + sourcemap: true, + target: "esnext", + }, +}); From a3804822b5f457b757da9d72c56b0d37de31e248 Mon Sep 17 00:00:00 2001 From: James Reetzke Date: Thu, 12 Feb 2026 00:06:59 -0500 Subject: [PATCH 06/17] milestone 6+7: remove @moq/hang-ui, clean up @moq/hang MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Milestone 6 — Remove @moq/hang-ui: - Delete js/hang-ui/ package entirely - Remove @moq/hang-ui dep from hang-demo/package.json - Remove js/hang-ui from root package.json workspaces - Delete doc/js/@moq/hang-ui.md - Update sidebar: replace @moq/hang-ui with @moq/watch, @moq/publish, @moq/ui-core - Update doc/index.md, doc/js/index.md, doc/js/env/web.md references - Update doc/js/@moq/hang/watch.md and publish.md SolidJS sections - Update doc/js/@moq/signals.md import reference - Update all @moq/hang/watch/element → @moq/watch/element imports in docs - Update all @moq/hang/publish/element → @moq/publish/element imports in docs - Update CLAUDE.md: remove hang-ui from structure, update tooling - Update README.md: remove @moq/hang-ui row, update @moq/ui-core description - Create doc pages: @moq/watch.md, @moq/publish.md, @moq/ui-core.md (fix dead links) Milestone 7 — Clean up @moq/hang: - Remove unused deps: comlink, async-mutex (only used by extracted watch/publish) - Rewrite js/hang/README.md for core-only scope (catalog, container, support) - Rewrite doc/js/@moq/hang/index.md for core-only scope - Update README.md @moq/hang description in package table All packages pass bun run check (TypeScript + VitePress build, zero dead links). --- CLAUDE.md | 11 +- README.md | 5 +- bun.lock | 98 +---------------- doc/.vitepress/config.ts | 4 +- doc/index.md | 3 +- doc/js/@moq/hang-ui.md | 68 ------------ doc/js/@moq/hang/index.md | 196 ++++----------------------------- doc/js/@moq/hang/publish.md | 23 ++-- doc/js/@moq/hang/watch.md | 25 ++--- doc/js/@moq/publish.md | 81 ++++++++++++++ doc/js/@moq/signals.md | 2 +- doc/js/@moq/ui-core.md | 40 +++++++ doc/js/@moq/watch.md | 81 ++++++++++++++ doc/js/env/web.md | 23 ++-- doc/js/index.md | 44 +++++--- js/hang-demo/package.json | 1 - js/hang-ui/README.md | 103 ------------------ js/hang-ui/package.json | 29 ----- js/hang-ui/src/vite-env.d.ts | 1 - js/hang-ui/tsconfig.json | 11 -- js/hang-ui/vite.config.ts | 18 --- js/hang/README.md | 205 ++++------------------------------- js/hang/package.json | 2 - package.json | 1 - 24 files changed, 322 insertions(+), 753 deletions(-) delete mode 100644 doc/js/@moq/hang-ui.md create mode 100644 doc/js/@moq/publish.md create mode 100644 doc/js/@moq/ui-core.md create mode 100644 doc/js/@moq/watch.md delete mode 100644 js/hang-ui/README.md delete mode 100644 js/hang-ui/package.json delete mode 100644 js/hang-ui/src/vite-env.d.ts delete mode 100644 js/hang-ui/tsconfig.json delete mode 100644 js/hang-ui/vite.config.ts diff --git a/CLAUDE.md b/CLAUDE.md index 3ffb8ec11..30a7a84fb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,7 +30,7 @@ The project contains multiple layers of protocols: 4. **hang** - Media-specific encoding/decoding on top of `moq-lite`. Contains: - catalog: a JSON track containing a description of other tracks and their properties (for WebCodecs). - container: each frame consists of a timestamp and codec bitstream - - hang-ui: Solid.js Web Components for media playback/publishing UI + - watch/publish: dedicated packages for subscribing/publishing with optional SolidJS UI overlays 5. **application** - Users building on top of `moq-lite` or `hang` Key architectural rule: The CDN/relay does not know anything about media. Anything in the `moq` layer should be generic, using rules on the wire on how to deliver content. @@ -56,11 +56,10 @@ Key architectural rule: The CDN/relay does not know anything about media. Anythi signals/ # Reactive signals library (published as @moq/signals) token/ # JWT token generation (published as @moq/token) clock/ # Clock example (published as @moq/clock) - hang/ # Media layer (published as @moq/hang) + hang/ # Core media layer: catalog, container, support (published as @moq/hang) ui-core/ # Shared UI components (published as @moq/ui-core) - watch/ # Watch/subscribe to streams (published as @moq/watch) - publish/ # Publish media to streams (published as @moq/publish) - hang-ui/ # Web Components UI (published as @moq/hang-ui) + watch/ # Watch/subscribe to streams + UI (published as @moq/watch) + publish/ # Publish media to streams + UI (published as @moq/publish) hang-demo/ # Demo applications /doc/ # Documentation site (VitePress, deployed via Cloudflare) @@ -80,7 +79,7 @@ Key architectural rule: The CDN/relay does not know anything about media. Anythi - **Common**: Use `just` for common development tasks - **Rust**: Use `cargo` for Rust-specific operations - **Formatting/Linting**: Biome for JS/TS formatting and linting -- **UI**: Solid.js for Web Components in `hang-ui` +- **UI**: Solid.js for Web Components in `@moq/watch/ui` and `@moq/publish/ui` - **Builds**: Nix flake for reproducible builds (optional) ## Testing Approach diff --git a/README.md b/README.md index 41e91c3da..c0478a667 100644 --- a/README.md +++ b/README.md @@ -127,12 +127,11 @@ This repository provides both [Rust](/rs) and [TypeScript](/js) libraries with s |------------------------------------------|--------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------| | **[@moq/lite](js/lite)** | The core pub/sub transport protocol. Intended for browsers, but can be run server-side with a WebTransport polyfill. | [![npm](https://img.shields.io/npm/v/@moq/lite)](https://www.npmjs.com/package/@moq/lite) | | **[@moq/token](js/token)** | Authentication library & CLI for JS/TS environments (see [Authentication](doc/concept/authentication.md)) | [![npm](https://img.shields.io/npm/v/@moq/token)](https://www.npmjs.com/package/@moq/token) | -| **[@moq/hang](js/hang)** | Media-specific encoding/streaming layered on top of `moq-lite`. Provides both a Javascript API and Web Components. | [![npm](https://img.shields.io/npm/v/@moq/hang)](https://www.npmjs.com/package/@moq/hang) | +| **[@moq/hang](js/hang)** | Core media library: catalog, container, and support. Shared by `@moq/watch` and `@moq/publish`. | [![npm](https://img.shields.io/npm/v/@moq/hang)](https://www.npmjs.com/package/@moq/hang) | | **[@moq/hang-demo](js/hang-demo)** | Examples using `@moq/hang`. | | | **[@moq/watch](js/watch)** | Subscribe to and render MoQ broadcasts (Web Component + JS API). | [![npm](https://img.shields.io/npm/v/@moq/watch)](https://www.npmjs.com/package/@moq/watch) | | **[@moq/publish](js/publish)** | Publish media to MoQ broadcasts (Web Component + JS API). | [![npm](https://img.shields.io/npm/v/@moq/publish)](https://www.npmjs.com/package/@moq/publish) | -| **[@moq/ui-core](js/ui-core)** | Shared UI components (Button, Icon, Stats, CSS theme) used by hang-ui. | [![npm](https://img.shields.io/npm/v/@moq/ui-core)](https://www.npmjs.com/package/@moq/ui-core) | -| **[@moq/hang-ui](js/hang-ui)**. | UI Components that interact with the Hang Web Components using SolidJS. | [![npm](https://img.shields.io/npm/v/@moq/hang-ui)](https://www.npmjs.com/package/@moq/hang-ui) | +| **[@moq/ui-core](js/ui-core)** | Shared UI components (Button, Icon, Stats, CSS theme) used by `@moq/watch/ui` and `@moq/publish/ui`. | [![npm](https://img.shields.io/npm/v/@moq/ui-core)](https://www.npmjs.com/package/@moq/ui-core) | ## Documentation diff --git a/bun.lock b/bun.lock index e5ab9013a..99513bc0e 100644 --- a/bun.lock +++ b/bun.lock @@ -43,8 +43,6 @@ "@moq/lite": "workspace:^", "@moq/signals": "workspace:^", "@svta/cml-iso-bmff": "^1.0.0-alpha.9", - "async-mutex": "^0.5.0", - "comlink": "^4.4.2", "zod": "^4.1.5", }, "devDependencies": { @@ -60,7 +58,6 @@ "version": "0.1.0", "dependencies": { "@moq/hang": "workspace:^", - "@moq/hang-ui": "workspace:^", "@moq/publish": "workspace:^", "@moq/watch": "workspace:^", }, @@ -74,21 +71,6 @@ "vite-plugin-solid": "^2.11.10", }, }, - "js/hang-ui": { - "name": "@moq/hang-ui", - "version": "0.1.2", - "devDependencies": { - "@types/audioworklet": "^0.0.77", - "@typescript/lib-dom": "npm:@types/web@^0.0.241", - "rimraf": "^6.0.1", - "solid-element": "^1.9.1", - "solid-js": "^1.9.10", - "typescript": "^5.9.2", - "unplugin-solid": "^1.0.0", - "vite": "^7.3.1", - "vite-plugin-solid": "^2.11.10", - }, - }, "js/lite": { "name": "@moq/lite", "version": "0.1.2", @@ -458,8 +440,6 @@ "@moq/hang-demo": ["@moq/hang-demo@workspace:js/hang-demo"], - "@moq/hang-ui": ["@moq/hang-ui@workspace:js/hang-ui"], - "@moq/lite": ["@moq/lite@workspace:js/lite"], "@moq/publish": ["@moq/publish@workspace:js/publish"], @@ -486,7 +466,7 @@ "@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="], - "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], + "@rollup/pluginutils": ["@rollup/pluginutils@4.2.1", "", { "dependencies": { "estree-walker": "^2.0.1", "picomatch": "^2.2.2" } }, "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.55.1", "", { "os": "android", "cpu": "arm" }, "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg=="], @@ -794,8 +774,6 @@ "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], - "comlink": ["comlink@4.4.2", "", {}, "sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g=="], - "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], "commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], @@ -1410,10 +1388,6 @@ "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], - "unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], - - "unplugin-solid": ["unplugin-solid@1.0.0", "", { "dependencies": { "@babel/core": "^7.28.3", "@rollup/pluginutils": "^5.2.0", "babel-preset-solid": "^1.9.9", "merge-anything": "^6.0.6", "solid-refresh": "^0.7.5", "unplugin": "^2.3.10", "vitefu": "^1.1.1" }, "peerDependencies": { "solid-js": "^1.9.9" } }, "sha512-pv1CS3XMtf3WwX8Dq9Bvo4qH6mfjN2xOgbaPcnqW1dLhyP/JQCvueGEsN0dYIZ4JvxaD/G/Ot1JnBzNQGHkfeA=="], - "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], "url-join": ["url-join@4.0.1", "", {}, "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="], @@ -1440,8 +1414,6 @@ "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="], - "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "wide-align": ["wide-align@1.1.5", "", { "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg=="], @@ -1480,8 +1452,6 @@ "@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], - "@moq/hang-ui/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], - "@moq/publish/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], "@moq/ui-core/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], @@ -1490,6 +1460,8 @@ "@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], + "@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], @@ -1554,20 +1526,12 @@ "unenv/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - "unplugin-solid/merge-anything": ["merge-anything@6.0.6", "", { "dependencies": { "is-what": "^5.2.0" } }, "sha512-F3K1W45PvTjRZzbcYIhXntNr8cux00gUxR8IzNPPG+80gNlAHZGVBwFyN4x5yjw/7QkLPKDbRQBK4KrJKo69mw=="], - - "unplugin-solid/solid-refresh": ["solid-refresh@0.7.5", "", { "dependencies": { "@babel/generator": "^7.23.6", "@babel/types": "^7.23.6" }, "peerDependencies": { "solid-js": "^1.3" } }, "sha512-ZYMbjWsy7IwSF3+oZCNnReiTYSyCAFRvC7oLUKxxh1wPa6/6YIWqsxa+Ma2kM4F/ypWT69B1c0fmKeZRdLueGw=="], - - "vite-plugin-html/@rollup/pluginutils": ["@rollup/pluginutils@4.2.1", "", { "dependencies": { "estree-walker": "^2.0.1", "picomatch": "^2.2.2" } }, "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ=="], - "vitepress/vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="], "wrangler/esbuild": ["esbuild@0.27.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.0", "@esbuild/android-arm": "0.27.0", "@esbuild/android-arm64": "0.27.0", "@esbuild/android-x64": "0.27.0", "@esbuild/darwin-arm64": "0.27.0", "@esbuild/darwin-x64": "0.27.0", "@esbuild/freebsd-arm64": "0.27.0", "@esbuild/freebsd-x64": "0.27.0", "@esbuild/linux-arm": "0.27.0", "@esbuild/linux-arm64": "0.27.0", "@esbuild/linux-ia32": "0.27.0", "@esbuild/linux-loong64": "0.27.0", "@esbuild/linux-mips64el": "0.27.0", "@esbuild/linux-ppc64": "0.27.0", "@esbuild/linux-riscv64": "0.27.0", "@esbuild/linux-s390x": "0.27.0", "@esbuild/linux-x64": "0.27.0", "@esbuild/netbsd-arm64": "0.27.0", "@esbuild/netbsd-x64": "0.27.0", "@esbuild/openbsd-arm64": "0.27.0", "@esbuild/openbsd-x64": "0.27.0", "@esbuild/openharmony-arm64": "0.27.0", "@esbuild/sunos-x64": "0.27.0", "@esbuild/win32-arm64": "0.27.0", "@esbuild/win32-ia32": "0.27.0", "@esbuild/win32-x64": "0.27.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA=="], "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - "@moq/hang-ui/vite/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], - "@moq/publish/vite/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], "@moq/ui-core/vite/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], @@ -1576,10 +1540,6 @@ "module-lookup-amd/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "unplugin-solid/merge-anything/is-what": ["is-what@5.5.0", "", {}, "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw=="], - - "vite-plugin-html/@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "vitepress/vite/esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], "wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A=="], @@ -1634,58 +1594,6 @@ "wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.0", "", { "os": "win32", "cpu": "x64" }, "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg=="], - "@moq/hang-ui/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.2", "", { "os": "android", "cpu": "arm64" }, "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.2", "", { "os": "android", "cpu": "x64" }, "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.2", "", { "os": "linux", "cpu": "arm" }, "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.2", "", { "os": "none", "cpu": "x64" }, "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ=="], - - "@moq/hang-ui/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], - "@moq/publish/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], "@moq/publish/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="], diff --git a/doc/.vitepress/config.ts b/doc/.vitepress/config.ts index 0004d6a9e..544501341 100644 --- a/doc/.vitepress/config.ts +++ b/doc/.vitepress/config.ts @@ -133,7 +133,9 @@ export default defineConfig({ { text: "Publish", link: "/js/@moq/hang/publish" }, ], }, - { text: "@moq/hang-ui", link: "/js/@moq/hang-ui" }, + { text: "@moq/watch", link: "/js/@moq/watch" }, + { text: "@moq/publish", link: "/js/@moq/publish" }, + { text: "@moq/ui-core", link: "/js/@moq/ui-core" }, { text: "@moq/token", link: "/js/@moq/token" }, { text: "@moq/signals", link: "/js/@moq/signals" }, { text: "@moq/web-transport-ws", link: "/js/@moq/web-transport-ws" }, diff --git a/doc/index.md b/doc/index.md index 930b3c45e..952e7228e 100644 --- a/doc/index.md +++ b/doc/index.md @@ -122,5 +122,6 @@ Or run on [native](/js/env/native) with polyfills via Node/Bun/Deno. Some highlights: - [@moq/lite](/js/@moq/lite) - Performs the core asynchronous networking. - [@moq/hang](/js/@moq/hang/) - Performs any media stuff: capture, encode, transmux, decode, render. -- [@moq/hang-ui](/js/@moq/hang-ui) - A simple web UI for those too lazy to vibe code one. +- [@moq/watch](/js/@moq/watch) - Subscribe to and render MoQ broadcasts. +- [@moq/publish](/js/@moq/publish) - Publish media to MoQ broadcasts. - [...and more](/js/) diff --git a/doc/js/@moq/hang-ui.md b/doc/js/@moq/hang-ui.md deleted file mode 100644 index b1bf7684b..000000000 --- a/doc/js/@moq/hang-ui.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: "@moq/hang-ui" -description: Ready-made UI components for MoQ playback and publishing ---- - -# @moq/hang-ui -A library of Web Components for MoQ media playback and publishing. -Drop them into your HTML and get a full-featured player or publisher with zero JavaScript required. - -## Installation -```bash -bun add @moq/hang-ui -# or -npm add @moq/hang-ui -``` - -TODO: Jsdelivr CDN - -## \ - -A video player with controls for watching MoQ streams. - -```html - - - - - -``` - -**Included controls:** -- Play/pause button -- Volume slider -- Latency slider -- Quality selector -- Fullscreen button -- Buffering indicator -- Stats panel - -## \ - -A publishing interface with source selection and controls. - -```html - - - - - -``` - -**Included controls:** -- Source selector (camera, screen, microphone, file) -- Camera picker (if multiple cameras available) -- Microphone picker (if multiple mics available) -- Publishing status indicator -- Stats panel - -## Full Control -If you want full control over the interface, use the underlying `` and `` elements from `@moq/hang` directly. - The `-ui` components just add the control overlay. - -```html - - - - -``` diff --git a/doc/js/@moq/hang/index.md b/doc/js/@moq/hang/index.md index e618a71d8..9d98f28cb 100644 --- a/doc/js/@moq/hang/index.md +++ b/doc/js/@moq/hang/index.md @@ -1,6 +1,6 @@ --- title: "@moq/hang" -description: Media library with Web Components +description: Core media library (catalog, container, support) --- # @moq/hang @@ -8,16 +8,16 @@ description: Media library with Web Components [![npm](https://img.shields.io/npm/v/@moq/hang)](https://www.npmjs.com/package/@moq/hang) [![TypeScript](https://img.shields.io/badge/TypeScript-ready-blue.svg)](https://www.typescriptlang.org/) -High-level media library for real-time streaming using [Media over QUIC](https://moq.dev), built on top of [@moq/lite](/js/@moq/lite). +Core media library for [Media over QUIC](https://moq.dev), built on top of [@moq/lite](/js/@moq/lite). Provides shared primitives used by [`@moq/watch`](/js/@moq/watch) and [`@moq/publish`](/js/@moq/publish). ## Overview `@moq/hang` provides: -- **Web Components** - Easiest way to add MoQ to your page -- **JavaScript API** - Advanced control for custom applications -- **WebCodecs integration** - Hardware-accelerated encoding/decoding -- **Reactive state** - Built on `@moq/signals` +- **Catalog** - JSON track describing other tracks and their codec properties +- **Container** - CMAF-based framing (timestamp + codec bitstream) +- **Support** - `` Web Component for browser capability detection +- **Utilities** - Hex encoding, libav polyfill, WebCodecs hacks ## Installation @@ -28,194 +28,46 @@ npm add @moq/hang pnpm add @moq/hang ``` -## Web Components +## Web Component -The fastest way to add MoQ to your web page. See [Web Components](/js/env/web) for full details. +### `` -### Publishing +Display browser support information: ```html - - - - -``` - -[Learn more about publishing](/js/@moq/hang/publish) - -### Watching - -```html - - - - - - + ``` ## JavaScript API -For advanced use cases: - ```typescript import * as Hang from "@moq/hang"; -// Create connection -const connection = new Hang.Connection("https://relay.example.com/anon"); - -// Publishing media -const publish = new Hang.Publish.Broadcast(connection, { - enabled: true, - name: "alice", - video: { enabled: true, device: "camera" }, - audio: { enabled: true }, -}); - -// Subscribing to media -const watch = new Hang.Watch.Broadcast(connection, { - enabled: true, - name: "alice", - video: { enabled: true }, - audio: { enabled: true }, -}); - -// Everything is reactive -publish.name.set("bob"); -watch.volume.set(0.8); -``` - -## Features - -### Real-time Latency +// Catalog — describes tracks and their codec properties +import * as Catalog from "@moq/hang/catalog"; -Uses WebTransport and WebCodecs for sub-second latency: - -```typescript -const watch = new Hang.Watch.Broadcast(connection, { - name: "live-stream", - // Latency optimizations - video: { enabled: true }, -}); +// Container — CMAF framing (timestamp + codec bitstream) +import * as Container from "@moq/hang/container"; ``` -### Device Selection - -Choose camera or screen: +For watching and publishing, use the dedicated packages: ```typescript -const publish = new Hang.Publish.Broadcast(connection, { - name: "my-stream", - video: { - enabled: true, - device: "camera", // or "screen" - }, -}); - -// Switch devices -publish.video.device.set("screen"); +import * as Watch from "@moq/watch"; +import * as Publish from "@moq/publish"; ``` -### Quality Control - -Control encoding quality: - -```typescript -const publish = new Hang.Publish.Broadcast(connection, { - name: "my-stream", - video: { - enabled: true, - bitrate: 2_500_000, // 2.5 Mbps - framerate: 30, - }, -}); -``` - -### Playback Controls - -```typescript -const watch = new Hang.Watch.Broadcast(connection, { - name: "stream", -}); - -// Pause/resume -watch.paused.set(true); -watch.paused.set(false); - -// Volume -watch.muted.set(false); -watch.volume.set(0.8); -``` - -## Reactive State - -Everything uses signals from `@moq/signals`: - -```typescript -import { react } from "@moq/signals/react"; - -const publish = document.querySelector("hang-publish") as HangPublish; - -// Convert to React signal -const videoSource = react(publish.video.media); - -useEffect(() => { - previewVideo.srcObject = videoSource(); -}, [videoSource]); -``` - -## Supported Codecs - -**Video:** -- H.264 (AVC) - Best compatibility -- H.265 (HEVC) - Better compression -- VP8 / VP9 - Open codec -- AV1 - Latest, best compression - -**Audio:** -- Opus - Best for voice/music -- AAC - Good compatibility - -Codec selection is automatic based on browser support. - -## Browser Support - -Requires: -- **WebTransport** - Chrome 97+, Edge 97+ -- **WebCodecs** - Same browsers -- **WebAudio** - All modern browsers - -## Examples - -Check out [hang-demo](https://github.com/moq-dev/moq/tree/main/js/hang-demo) for: - -- Video conferencing -- Screen sharing -- Chat integration -- Quality selection UI - -[View more examples](https://github.com/moq-dev/moq/tree/main/js) - -## Framework Integration - -Works with any framework: +## Related Packages -- **React** - Via `@moq/signals/react` -- **SolidJS** - Via `@moq/signals/solid` or `@moq/hang-ui` -- **Vue** - Via `@moq/signals/vue` -- **Vanilla JS** - Direct Web Components +- **[@moq/watch](/js/@moq/watch)** — Subscribe to and render MoQ broadcasts +- **[@moq/publish](/js/@moq/publish)** — Publish media to MoQ broadcasts +- **[@moq/ui-core](/js/@moq/ui-core)** — Shared UI components +- **[@moq/lite](/js/@moq/lite)** — Core pub/sub transport protocol +- **[@moq/signals](/js/@moq/signals)** — Reactive signals library ## Protocol Specification diff --git a/doc/js/@moq/hang/publish.md b/doc/js/@moq/hang/publish.md index d5195d007..5cbd1f769 100644 --- a/doc/js/@moq/hang/publish.md +++ b/doc/js/@moq/hang/publish.md @@ -5,7 +5,7 @@ description: Publish camera, microphone, or screen to MoQ # Publishing Streams -This guide covers how to publish media to MoQ relays using `@moq/hang`. +This guide covers how to publish media to MoQ relays using `@moq/publish`. ## Web Component @@ -13,7 +13,7 @@ The simplest way to publish: ```html (null); @@ -287,20 +287,13 @@ function Publisher({ url, path }) { ## SolidJS Integration -Use `@moq/hang-ui`: +Use `@moq/publish/ui` for the SolidJS UI overlay: ```tsx -import { HangPublish } from "@moq/hang-ui/publish"; +import "@moq/publish/element"; +import "@moq/publish/ui"; -function Publisher(props) { - return ( - - ); -} +// The element wraps with controls ``` ## Authentication diff --git a/doc/js/@moq/hang/watch.md b/doc/js/@moq/hang/watch.md index 8007b62d7..2d9087be6 100644 --- a/doc/js/@moq/hang/watch.md +++ b/doc/js/@moq/hang/watch.md @@ -5,7 +5,7 @@ description: Subscribe to and render MoQ broadcasts # Watching Streams -This guide covers how to subscribe to and render MoQ broadcasts using `@moq/hang`. +This guide covers how to subscribe to and render MoQ broadcasts using `@moq/watch`. ## Web Component @@ -13,7 +13,7 @@ The simplest way to watch a stream: ```html { ```tsx import { useEffect, useRef } from "react"; -import "@moq/hang/watch/element"; -import type { HangWatch } from "@moq/hang"; +import "@moq/watch/element"; +import type HangWatch from "@moq/watch/element"; function VideoPlayer({ url, path }) { const watchRef = useRef(null); @@ -236,26 +236,19 @@ function VideoPlayer({ url, path }) { ## SolidJS Integration -Use `@moq/hang-ui` for native components: +Use `@moq/watch/ui` for the SolidJS UI overlay: ```tsx -import { HangWatch } from "@moq/hang-ui/watch"; +import "@moq/watch/element"; +import "@moq/watch/ui"; -function VideoPlayer(props) { - return ( - - ); -} +// The element wraps with controls ``` Or use Web Components directly: ```tsx -import "@moq/hang/watch/element"; +import "@moq/watch/element"; function VideoPlayer(props) { return ( diff --git a/doc/js/@moq/publish.md b/doc/js/@moq/publish.md new file mode 100644 index 000000000..bd04018d1 --- /dev/null +++ b/doc/js/@moq/publish.md @@ -0,0 +1,81 @@ +--- +title: "@moq/publish" +description: Publish media to MoQ broadcasts +--- + +# @moq/publish + +[![npm](https://img.shields.io/npm/v/@moq/publish)](https://www.npmjs.com/package/@moq/publish) +[![TypeScript](https://img.shields.io/badge/TypeScript-ready-blue.svg)](https://www.typescriptlang.org/) + +Publish media to MoQ broadcasts. Provides both a JavaScript API and a `` Web Component, plus an optional `` SolidJS overlay. + +## Installation + +```bash +bun add @moq/publish +# or +npm add @moq/publish +``` + +## Web Component + +```html + + + + + +``` + +**Attributes:** +- `url` (required) — Relay server URL +- `path` (required) — Broadcast path/name +- `device` — "camera" or "screen" (default: "camera") +- `audio` — Enable audio capture (boolean) +- `video` — Enable video capture (boolean) +- `controls` — Show publishing controls (boolean) + +## UI Overlay + +Import `@moq/publish/ui` for a SolidJS-powered overlay with device selection and publishing controls: + +```html + + + + +``` + +## JavaScript API + +```typescript +import * as Publish from "@moq/publish"; + +const broadcast = new Publish.Broadcast(connection, { + enabled: true, + name: "alice", + video: { enabled: true, device: "camera" }, + audio: { enabled: true }, +}); + +// Reactive controls +broadcast.video.device.set("screen"); +broadcast.name.set("bob"); +``` + +## Related Packages + +- **[@moq/watch](/js/@moq/watch)** — Subscribe to and render MoQ broadcasts +- **[@moq/hang](/js/@moq/hang/)** — Core media library (catalog, container, support) +- **[@moq/ui-core](/js/@moq/ui-core)** — Shared UI primitives +- **[@moq/lite](/js/@moq/lite)** — Core pub/sub transport protocol diff --git a/doc/js/@moq/signals.md b/doc/js/@moq/signals.md index d2e3a1596..dca25eab0 100644 --- a/doc/js/@moq/signals.md +++ b/doc/js/@moq/signals.md @@ -146,7 +146,7 @@ const vueCount = vue(count); All `@moq/hang` properties are signals: ```typescript -import "@moq/hang/watch/element"; +import "@moq/watch/element"; import { react } from "@moq/signals/react"; const watch = document.querySelector("hang-watch") as HangWatch; diff --git a/doc/js/@moq/ui-core.md b/doc/js/@moq/ui-core.md new file mode 100644 index 000000000..f1c833a9e --- /dev/null +++ b/doc/js/@moq/ui-core.md @@ -0,0 +1,40 @@ +--- +title: "@moq/ui-core" +description: Shared UI primitives for MoQ components +--- + +# @moq/ui-core + +[![npm](https://img.shields.io/npm/v/@moq/ui-core)](https://www.npmjs.com/package/@moq/ui-core) +[![TypeScript](https://img.shields.io/badge/TypeScript-ready-blue.svg)](https://www.typescriptlang.org/) + +Shared UI primitives used by `@moq/watch/ui` and `@moq/publish/ui`. Includes buttons, icons, stats panels, and a CSS theme. + +## Installation + +```bash +bun add @moq/ui-core +# or +npm add @moq/ui-core +``` + +## Components + +- **Button** — Styled button component +- **Icon** — SVG icon library (play, pause, mic, camera, etc.) +- **Stats** — Network statistics panel +- **CSS Theme** — Shared CSS variables and base styles + +## Usage + +This package is primarily consumed internally by `@moq/watch/ui` and `@moq/publish/ui`. You typically don't need to install it directly unless building custom UI on top of MoQ. + +```typescript +import { Button, Icon, Stats } from "@moq/ui-core"; +``` + +## Related Packages + +- **[@moq/watch](/js/@moq/watch)** — Subscribe to and render MoQ broadcasts +- **[@moq/publish](/js/@moq/publish)** — Publish media to MoQ broadcasts +- **[@moq/hang](/js/@moq/hang/)** — Core media library diff --git a/doc/js/@moq/watch.md b/doc/js/@moq/watch.md new file mode 100644 index 000000000..ae36234fa --- /dev/null +++ b/doc/js/@moq/watch.md @@ -0,0 +1,81 @@ +--- +title: "@moq/watch" +description: Subscribe to and render MoQ broadcasts +--- + +# @moq/watch + +[![npm](https://img.shields.io/npm/v/@moq/watch)](https://www.npmjs.com/package/@moq/watch) +[![TypeScript](https://img.shields.io/badge/TypeScript-ready-blue.svg)](https://www.typescriptlang.org/) + +Subscribe to and render MoQ broadcasts. Provides both a JavaScript API and a `` Web Component, plus an optional `` SolidJS overlay. + +## Installation + +```bash +bun add @moq/watch +# or +npm add @moq/watch +``` + +## Web Component + +```html + + + + + +``` + +**Attributes:** +- `url` (required) — Relay server URL +- `path` (required) — Broadcast path/name +- `controls` — Show playback controls (boolean) +- `paused` — Pause playback (boolean) +- `muted` — Mute audio (boolean) +- `volume` — Audio volume (0–1, default: 1) + +## UI Overlay + +Import `@moq/watch/ui` for a SolidJS-powered overlay with buffering indicator, stats panel, and playback controls: + +```html + + + + +``` + +## JavaScript API + +```typescript +import * as Watch from "@moq/watch"; + +const broadcast = new Watch.Broadcast(connection, { + enabled: true, + name: "alice", + video: { enabled: true }, + audio: { enabled: true }, +}); + +// Reactive controls +broadcast.volume.set(0.8); +broadcast.paused.set(false); +``` + +## Related Packages + +- **[@moq/publish](/js/@moq/publish)** — Publish media to MoQ broadcasts +- **[@moq/hang](/js/@moq/hang/)** — Core media library (catalog, container, support) +- **[@moq/ui-core](/js/@moq/ui-core)** — Shared UI primitives +- **[@moq/lite](/js/@moq/lite)** — Core pub/sub transport protocol diff --git a/doc/js/env/web.md b/doc/js/env/web.md index c9cd32654..93e57b987 100644 --- a/doc/js/env/web.md +++ b/doc/js/env/web.md @@ -32,7 +32,7 @@ Publish camera/microphone or screen as a MoQ broadcast. ```html - import "@moq/hang/watch/element"; + import "@moq/watch/element"; ```tsx import { useEffect, useRef } from "react"; -import "@moq/hang/watch/element"; +import "@moq/watch/element"; function VideoPlayer({ url, path }) { const ref = useRef(null); @@ -163,10 +163,10 @@ function VideoPlayer({ url, path }) { ### SolidJS -Use `@moq/hang-ui` for native SolidJS components, or use Web Components directly: +Use `@moq/watch/ui` and `@moq/publish/ui` for SolidJS UI overlays, or use Web Components directly: ```tsx -import "@moq/hang/watch/element"; +import "@moq/watch/element"; function VideoPlayer(props) { return ( @@ -193,7 +193,7 @@ function VideoPlayer(props) { @@ -190,7 +200,7 @@ createEffect(() => { }); ``` -Use `@moq/hang-ui` for ready-made SolidJS components. +Use `@moq/watch/ui` and `@moq/publish/ui` for ready-made SolidJS UI overlays. ## Demo Application diff --git a/js/hang-demo/package.json b/js/hang-demo/package.json index 6934f30d4..3a3ab6c53 100644 --- a/js/hang-demo/package.json +++ b/js/hang-demo/package.json @@ -13,7 +13,6 @@ }, "dependencies": { "@moq/hang": "workspace:^", - "@moq/hang-ui": "workspace:^", "@moq/watch": "workspace:^", "@moq/publish": "workspace:^" }, diff --git a/js/hang-ui/README.md b/js/hang-ui/README.md deleted file mode 100644 index 86d523e3a..000000000 --- a/js/hang-ui/README.md +++ /dev/null @@ -1,103 +0,0 @@ -

- Media over QUIC -

- -# @moq/hang-ui - -[![npm version](https://img.shields.io/npm/v/@moq/hang-ui)](https://www.npmjs.com/package/@moq/hang-ui) -[![TypeScript](https://img.shields.io/badge/TypeScript-ready-blue.svg)](https://www.typescriptlang.org/) - -A TypeScript library for interacting with @moq/hang Web Components. Provides methods to control playback and publish sources, as well as status of the connection. - -## Installation - -```bash -npm add @moq/hang-ui -# or -pnpm add @moq/hang-ui -yarn add @moq/hang-ui -bun add @moq/hang-ui -``` - -## Web Components - -Currently, there are two Web Components provided by @moq/hang-ui: - -- `` -- `` - -Here's how you can use them (see also @moq/hang-demo for a complete example): - -```html - - - - - -``` - -```html - - - - - -``` - -## Project Structure -The `@moq/hang-ui` package is organized into modular components and utilities: - -```text -src/ -├── publish/ # Publishing UI components -│ ├── components/ # UI controls for publishing -│ ├── hooks/ # Custom Solid hooks for publish UI -│ ├── styles/ # CSS styles for publish UI -│ ├── context.tsx # Context provider for publish state -│ ├── element.tsx # Main publish UI component -│ └── index.tsx # Entry point for publish UI -│ -└── watch/ # Watching/playback UI components - ├── components/ # UI controls for watching - ├── hooks/ # Custom Solid hooks for watch UI - ├── styles/ # CSS styles for watch UI - ├── context.tsx # Context provider for watch state - ├── element.tsx # Main watch UI component - └── index.tsx # Entry point for watch UI -``` - -> **Note:** Shared UI components (Button, Icon, Stats, CSS variables) have been extracted into the [`@moq/ui-core`](../ui-core) package. - -### Module Overview - -#### **publish/** -Contains all UI components related to media publishing. It provides controls for selecting media sources (camera, screen, microphone, file) and managing the publishing state. - -- **MediaSourceSelector**: Allows users to choose their media source -- **PublishControls**: Main control panel for publishing -- **Source buttons**: Individual buttons for camera, screen, microphone, file, and "nothing" sources -- **PublishStatusIndicator**: Displays connection and publishing status - -#### **watch/** -Implements the video player UI with controls for watching live streams. Includes playback controls, quality selection, and buffering indicators. - -- **WatchControls**: Main control panel for the video player -- **PlayPauseButton**: Play/pause toggle -- **VolumeSlider**: Audio volume control -- **LatencySlider**: Adjust playback latency -- **QualitySelector**: Switch between quality levels -- **FullscreenButton**: Toggle fullscreen mode -- **BufferingIndicator**: Visual feedback during buffering -- **StatsButton**: Toggle statistics panel - -#### **@moq/ui-core** (external dependency) -Shared UI components consumed by this package. See [`@moq/ui-core`](../ui-core) for details. - -- **Button**: Reusable button component with consistent styling -- **Icon**: Icon wrapper component -- **Stats**: Real-time statistics monitoring for media streams -- **CSS utilities**: Theme variables, flexbox utilities, and component styles diff --git a/js/hang-ui/package.json b/js/hang-ui/package.json deleted file mode 100644 index b01cb5c3a..000000000 --- a/js/hang-ui/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "@moq/hang-ui", - "type": "module", - "version": "0.1.2", - "description": "Media over QUIC library UI components", - "license": "(MIT OR Apache-2.0)", - "repository": "github:moq-dev/moq", - "exports": {}, - "sideEffects": [], - "scripts": { - "build": "bun run clean && vite build && bun ../scripts/package.ts", - "check": "tsc --noEmit", - "clean": "rimraf dist", - "fix": "biome check src --fix", - "release": "bun ../scripts/release.ts" - }, - "peerDependencies": {}, - "devDependencies": { - "@types/audioworklet": "^0.0.77", - "@typescript/lib-dom": "npm:@types/web@^0.0.241", - "rimraf": "^6.0.1", - "solid-element": "^1.9.1", - "solid-js": "^1.9.10", - "typescript": "^5.9.2", - "unplugin-solid": "^1.0.0", - "vite": "^7.3.1", - "vite-plugin-solid": "^2.11.10" - } -} diff --git a/js/hang-ui/src/vite-env.d.ts b/js/hang-ui/src/vite-env.d.ts deleted file mode 100644 index 11f02fe2a..000000000 --- a/js/hang-ui/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/js/hang-ui/tsconfig.json b/js/hang-ui/tsconfig.json deleted file mode 100644 index 92fab6440..000000000 --- a/js/hang-ui/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "rootDir": ".", - "jsx": "preserve", - "jsxImportSource": "solid-js", - "outDir": "dist", - "emitDeclarationOnly": true - }, - "include": ["src"] -} diff --git a/js/hang-ui/vite.config.ts b/js/hang-ui/vite.config.ts deleted file mode 100644 index 2050b31e5..000000000 --- a/js/hang-ui/vite.config.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { resolve } from "path"; -import { defineConfig } from "vite"; -import solidPlugin from "vite-plugin-solid"; - -export default defineConfig({ - plugins: [solidPlugin()], - build: { - lib: { - entry: {}, - formats: ["es"], - }, - rollupOptions: { - external: ["@moq/hang", "@moq/lite", "@moq/signals", "@moq/ui-core", "@moq/publish"], - }, - sourcemap: true, - target: "esnext", - }, -}); diff --git a/js/hang/README.md b/js/hang/README.md index 5a725a1d5..beec0ca3d 100644 --- a/js/hang/README.md +++ b/js/hang/README.md @@ -7,19 +7,14 @@ [![npm version](https://img.shields.io/npm/v/@moq/hang)](https://www.npmjs.com/package/@moq/hang) [![TypeScript](https://img.shields.io/badge/TypeScript-ready-blue.svg)](https://www.typescriptlang.org/) -A TypeScript library for real-time media streaming using [Media over QUIC](https://moq.dev/) (MoQ), supported by modern web browsers. - -**`@moq/hang`** provides high-level media components for live audio and video streaming, built on top of [`@moq/lite`](../moq). -It uses new web APIs like WebCodecs, WebTransport, and Web Components. - -> **Note:** This project is a [fork](https://moq.dev/blog/transfork) of the [IETF MoQ specification](https://datatracker.ietf.org/group/moq/documents/), optimized for practical deployment with a narrower focus and exponentially simpler implementation. +Core media library for [Media over QUIC](https://moq.dev/) (MoQ). Provides shared primitives used by [`@moq/watch`](../watch) and [`@moq/publish`](../publish), built on top of [`@moq/lite`](../lite). ## Features -- 🎥 **Real-time latency** via WebTransport and WebCodecs. -- 🎵 **Low-level API** for advanced use cases, such as processing individual frames. -- 🧩 **Web Components** for easy integration. -- 🔄 **Reactive** Easy to use with React and SolidJS adapters. +- **Catalog** — JSON track describing other tracks and their codec properties (audio, video, chat, location) +- **Container** — CMAF-based framing: timestamp + codec bitstream encoding/decoding +- **Support** — `` Web Component for browser capability detection +- **Utilities** — Hex encoding, libav polyfill, WebCodecs hacks ## Installation @@ -31,198 +26,46 @@ yarn add @moq/hang bun add @moq/hang ``` -## Web Components (Easiest) - -The fastest way to add MoQ to your web page. -Check out the [hang-demo](../hang-demo) folder for working examples. - -There's also a Javascript API for more advanced use cases; see below. - -```html - - - - - - - - - - - - - - - - - - - -``` - -### Tree-Shaking -Javascript bundlers often perform dead code elimination. -This can have unfortunate side effects, as it can remove the code that registers these components. - -To attempt to mitigate this, you have to explicitly import components with the `/element` suffix. -Your bundler *should* be smart enough to avoid tree-shaking but you may need to `export` any types just to ensure they are not removed. - -### Attributes -All of the web components support setting HTTML attributes and Javascript properties. -...what's the difference? - -HTML Attributes are strings. -Javacript properties are typed and reactive. - -`` will work, but it's not type-safe. -You can use DOM callbacks to detect when the attribute changes but it's not as convenient. - -Alternatively, you could perform the same thing with Javascript properties: -```tsx -const watch = document.querySelector("hang-watch") as HangWatch; -watch.volume.set(0.8); -``` - -This will actually set the `volume="0.8"` attribute on the element mostly because it's cool and useful when debugging. -But it's also useful because you can use the `.subscribe` method to receive an event on change. - - -### `` - -Subscribes to a hang broadcast and renders it. - -**Attributes:** -- `url` (required): The URL of the server, potentially authenticated via a `?jwt` token. -- `name` (required): The name of the broadcast. -- `controls`: Show simple playback controls. -- `paused`: Pause playback. -- `muted`: Mute audio playback. -- `volume`: Set the audio volume, only when `!muted`. - - -```html - - - - - - - -``` - -> **Note:** The `` element is now provided by [`@moq/watch`](../watch). See its [README](../watch/README.md) for full details. - - -### `` - -Publishes a microphone/camera or screen as a hang broadcast. - -**Attributes:** -- `url` (required): The URL of the server, potentially authenticated via a `?jwt` token. -- `name` (required): The name of the broadcast. -- `device`: "camera" or "screen". -- `audio`: Enable audio capture. -- `video`: Enable video capture -- `controls`: Show simple publishing controls - -```html - - - - - - -``` - -> **Note:** The `` element is now provided by [`@moq/publish`](../publish). See its [README](../publish/README.md) for full details. +## Web Component ### `` -A simple element that displays browser support. +Display browser support information: ```html - - + ``` - -## Javascript API - -**NOTE** This API is still evolving and may change in the future. -You're on your own when it comes to documentation... for now. +## JavaScript API ```typescript import * as Hang from "@moq/hang"; -// Create a new connection, available via `.established` -const connection = new Hang.Connection("https://cdn.moq.dev/anon"); - -// Publishing media — now in @moq/publish -import * as Publish from "@moq/publish"; -const publish = new Publish.Broadcast(connection, { - enabled: true, - name: "bob", - video: { enabled: true, device: "camera" }, -}); +// Catalog — describes tracks and their codec properties +import * as Catalog from "@moq/hang/catalog"; -// Subscribing to media — now in @moq/watch -import * as Watch from "@moq/watch"; -const watch = new Watch.Broadcast(connection, { - enabled: true, - name: "bob", - video: { enabled: true }, -}); - -// Note that virtually everything is reactive, so you can change settings at any time. -publish.name.set("alice"); -watch.audio.enabled.set(true); +// Container — CMAF framing (timestamp + codec bitstream) +import * as Container from "@moq/hang/container"; ``` -## Browser Compatibility - -This library requires modern browser features. -We're currently only testing the most recent versions of Chrome and sometimes Firefox. - -## Framework Integration +For watching and publishing, use the dedicated packages: -The Reactive API contains helpers to convert into React and SolidJS signals: - -```ts -import react from "@moq/signals/react"; -// same for solid +```typescript +import * as Watch from "@moq/watch"; +import * as Publish from "@moq/publish"; +``` -const publish = document.querySelector("hang-publish") as HangPublish; -const media = react(publish.video.media); +## Related Packages -/// Now you have a `react` signal that changes when the video source changes. -useEffect(() => { - video.srcObject = media(); -}, [media]); -``` +- **[@moq/watch](../watch)** — Subscribe to and render MoQ broadcasts +- **[@moq/publish](../publish)** — Publish media to MoQ broadcasts +- **[@moq/ui-core](../ui-core)** — Shared UI components +- **[@moq/lite](../lite)** — Core pub/sub transport protocol +- **[@moq/signals](../signals)** — Reactive signals library ## License diff --git a/js/hang/package.json b/js/hang/package.json index 5f6689f9a..5a4ba67e2 100644 --- a/js/hang/package.json +++ b/js/hang/package.json @@ -30,8 +30,6 @@ "@moq/lite": "workspace:^", "@moq/signals": "workspace:^", "@svta/cml-iso-bmff": "^1.0.0-alpha.9", - "async-mutex": "^0.5.0", - "comlink": "^4.4.2", "zod": "^4.1.5" }, "devDependencies": { diff --git a/package.json b/package.json index 4c67e76ff..ea1696504 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,6 @@ "js/ui-core", "js/watch", "js/publish", - "js/hang-ui", "js/hang-demo", "js/signals" ], From caf5bded23f01322dbcbf37845119986d2725b01 Mon Sep 17 00:00:00 2001 From: James Reetzke Date: Thu, 12 Feb 2026 10:25:20 -0500 Subject: [PATCH 07/17] fix: correct hang-*-ui doc examples to show wrapping pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The and elements work by discovering a nested / child — they don't accept url/path attributes directly. Updated all doc examples to show the correct usage: - doc/js/@moq/watch.md - doc/js/@moq/publish.md - doc/js/@moq/hang/watch.md - doc/js/@moq/hang/publish.md --- doc/js/@moq/hang/publish.md | 16 +++++++++++----- doc/js/@moq/hang/watch.md | 16 +++++++++++----- doc/js/@moq/publish.md | 13 ++++++++++--- doc/js/@moq/watch.md | 12 +++++++++--- 4 files changed, 41 insertions(+), 16 deletions(-) diff --git a/doc/js/@moq/hang/publish.md b/doc/js/@moq/hang/publish.md index 5cbd1f769..af5d092ef 100644 --- a/doc/js/@moq/hang/publish.md +++ b/doc/js/@moq/hang/publish.md @@ -287,13 +287,19 @@ function Publisher({ url, path }) { ## SolidJS Integration -Use `@moq/publish/ui` for the SolidJS UI overlay: +Use `@moq/publish/ui` for the SolidJS UI overlay. The `` element wraps a nested ``: -```tsx -import "@moq/publish/element"; -import "@moq/publish/ui"; +```html + -// The element wraps with controls + + + + + ``` ## Authentication diff --git a/doc/js/@moq/hang/watch.md b/doc/js/@moq/hang/watch.md index 2d9087be6..8d8f52324 100644 --- a/doc/js/@moq/hang/watch.md +++ b/doc/js/@moq/hang/watch.md @@ -236,13 +236,19 @@ function VideoPlayer({ url, path }) { ## SolidJS Integration -Use `@moq/watch/ui` for the SolidJS UI overlay: +Use `@moq/watch/ui` for the SolidJS UI overlay. The `` element wraps a nested ``: -```tsx -import "@moq/watch/element"; -import "@moq/watch/ui"; +```html + -// The element wraps with controls + + + + + ``` Or use Web Components directly: diff --git a/doc/js/@moq/publish.md b/doc/js/@moq/publish.md index bd04018d1..3684ecbe4 100644 --- a/doc/js/@moq/publish.md +++ b/doc/js/@moq/publish.md @@ -47,15 +47,22 @@ Import `@moq/publish/ui` for a SolidJS-powered overlay with device selection and ```html - + + + + ``` +The `` element automatically discovers the nested `` and wires up reactive controls. + ## JavaScript API ```typescript diff --git a/doc/js/@moq/watch.md b/doc/js/@moq/watch.md index ae36234fa..c21573a64 100644 --- a/doc/js/@moq/watch.md +++ b/doc/js/@moq/watch.md @@ -47,15 +47,21 @@ Import `@moq/watch/ui` for a SolidJS-powered overlay with buffering indicator, s ```html - + + + + ``` +The `` element automatically discovers the nested `` and wires up reactive controls. + ## JavaScript API ```typescript From 9281a812705233458157702f52cb43611f2b6b94 Mon Sep 17 00:00:00 2001 From: James Reetzke Date: Thu, 12 Feb 2026 10:35:38 -0500 Subject: [PATCH 08/17] fix: correct @moq/hang descriptions and UI overlay examples - Container includes both CMAF (fMP4) and Legacy formats, not just CMAF - Fix CMAF API names in code examples to match actual exports - Fix Utilities description: hex encoding, Opus polyfill, latency computation, browser workarounds - Fix hang-watch-ui / hang-publish-ui examples to show correct wrapping pattern (they discover a nested child element, not accept url/path attributes directly) Updated: doc/js/@moq/hang/index.md, js/hang/README.md, doc/js/@moq/watch.md, doc/js/@moq/publish.md, doc/js/@moq/hang/watch.md, doc/js/@moq/hang/publish.md --- doc/js/@moq/hang/index.md | 14 +++++++++----- js/hang/README.md | 14 +++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/doc/js/@moq/hang/index.md b/doc/js/@moq/hang/index.md index 9d98f28cb..b2ad0c756 100644 --- a/doc/js/@moq/hang/index.md +++ b/doc/js/@moq/hang/index.md @@ -14,10 +14,10 @@ Core media library for [Media over QUIC](https://moq.dev), built on top of [@moq `@moq/hang` provides: -- **Catalog** - JSON track describing other tracks and their codec properties -- **Container** - CMAF-based framing (timestamp + codec bitstream) -- **Support** - `` Web Component for browser capability detection -- **Utilities** - Hex encoding, libav polyfill, WebCodecs hacks +- **Catalog** - JSON track describing other tracks and their codec properties (audio, video, chat, location, etc.) +- **Container** - Media framing in two formats: CMAF (fMP4) and Legacy (varint-timestamp + raw codec bitstream) +- **Support** - Browser capability detection and `` Web Component +- **Utilities** - Hex encoding, Opus audio polyfill (libav), latency computation, browser detection workarounds ## Installation @@ -50,8 +50,12 @@ import * as Hang from "@moq/hang"; // Catalog — describes tracks and their codec properties import * as Catalog from "@moq/hang/catalog"; -// Container — CMAF framing (timestamp + codec bitstream) +// Container — media framing (CMAF and Legacy formats) import * as Container from "@moq/hang/container"; + +// CMAF (fMP4) and Legacy (varint-timestamp + raw bitstream) are both available: +// Container.Cmaf — createVideoInitSegment, createAudioInitSegment, encodeDataSegment, decodeDataSegment, etc. +// Container.Legacy — Producer / Consumer classes ``` For watching and publishing, use the dedicated packages: diff --git a/js/hang/README.md b/js/hang/README.md index beec0ca3d..0980e56df 100644 --- a/js/hang/README.md +++ b/js/hang/README.md @@ -11,10 +11,10 @@ Core media library for [Media over QUIC](https://moq.dev/) (MoQ). Provides share ## Features -- **Catalog** — JSON track describing other tracks and their codec properties (audio, video, chat, location) -- **Container** — CMAF-based framing: timestamp + codec bitstream encoding/decoding -- **Support** — `` Web Component for browser capability detection -- **Utilities** — Hex encoding, libav polyfill, WebCodecs hacks +- **Catalog** — JSON track describing other tracks and their codec properties (audio, video, chat, location, etc.) +- **Container** — Media framing in two formats: CMAF (fMP4) and Legacy (varint-timestamp + raw codec bitstream) +- **Support** — Browser capability detection and `` Web Component +- **Utilities** — Hex encoding, Opus audio polyfill (libav), latency computation, browser detection workarounds ## Installation @@ -48,8 +48,12 @@ import * as Hang from "@moq/hang"; // Catalog — describes tracks and their codec properties import * as Catalog from "@moq/hang/catalog"; -// Container — CMAF framing (timestamp + codec bitstream) +// Container — media framing (CMAF and Legacy formats) import * as Container from "@moq/hang/container"; + +// CMAF (fMP4) and Legacy (varint-timestamp + raw bitstream) are both available: +// Container.Cmaf — createVideoInitSegment, createAudioInitSegment, encodeDataSegment, decodeDataSegment, etc. +// Container.Legacy — Producer / Consumer classes ``` For watching and publishing, use the dedicated packages: From a0ac0679fe5b8745ed940db9ffb8af1d2b2d7322 Mon Sep 17 00:00:00 2001 From: James Reetzke Date: Thu, 12 Feb 2026 11:24:30 -0500 Subject: [PATCH 09/17] fix: enable UI type-checking in @moq/watch and @moq/publish - tsconfig.json now includes src/ui with SolidJS JSX settings, used by the IDE and check script for full type coverage - tsconfig.build.json excludes src/ui for tsc -b declaration emit (Vite handles UI compilation) - Added vite-env.d.ts for Vite-specific import types (?raw, ?inline) Affected packages: @moq/watch, @moq/publish --- js/publish/package.json | 2 +- js/publish/src/vite-env.d.ts | 1 + js/publish/tsconfig.build.json | 7 +++++++ js/publish/tsconfig.json | 6 +++--- js/watch/package.json | 2 +- js/watch/src/vite-env.d.ts | 1 + js/watch/tsconfig.build.json | 7 +++++++ js/watch/tsconfig.json | 6 +++--- 8 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 js/publish/src/vite-env.d.ts create mode 100644 js/publish/tsconfig.build.json create mode 100644 js/watch/src/vite-env.d.ts create mode 100644 js/watch/tsconfig.build.json diff --git a/js/publish/package.json b/js/publish/package.json index 126b1eace..e0da531ba 100644 --- a/js/publish/package.json +++ b/js/publish/package.json @@ -15,7 +15,7 @@ "./src/ui/index.tsx" ], "scripts": { - "build": "rimraf dist && tsc -b && vite build && bun ../scripts/package.ts", + "build": "rimraf dist && tsc -b tsconfig.build.json && vite build && bun ../scripts/package.ts", "check": "tsc --noEmit", "release": "bun ../scripts/release.ts" }, diff --git a/js/publish/src/vite-env.d.ts b/js/publish/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/js/publish/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/js/publish/tsconfig.build.json b/js/publish/tsconfig.build.json new file mode 100644 index 000000000..ca0a3803e --- /dev/null +++ b/js/publish/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "composite": true + }, + "exclude": ["src/ui"] +} diff --git a/js/publish/tsconfig.json b/js/publish/tsconfig.json index 7089f27a3..eec8246c2 100644 --- a/js/publish/tsconfig.json +++ b/js/publish/tsconfig.json @@ -3,8 +3,8 @@ "compilerOptions": { "rootDir": ".", "outDir": "dist", - "composite": true + "jsx": "preserve", + "jsxImportSource": "solid-js" }, - "include": ["src"], - "exclude": ["src/ui"] + "include": ["src"] } diff --git a/js/watch/package.json b/js/watch/package.json index 3b41cac5f..aabd99be9 100644 --- a/js/watch/package.json +++ b/js/watch/package.json @@ -15,7 +15,7 @@ "./src/ui/index.tsx" ], "scripts": { - "build": "rimraf dist && tsc -b && vite build && bun ../scripts/package.ts", + "build": "rimraf dist && tsc -b tsconfig.build.json && vite build && bun ../scripts/package.ts", "check": "tsc --noEmit", "release": "bun ../scripts/release.ts" }, diff --git a/js/watch/src/vite-env.d.ts b/js/watch/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/js/watch/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/js/watch/tsconfig.build.json b/js/watch/tsconfig.build.json new file mode 100644 index 000000000..ca0a3803e --- /dev/null +++ b/js/watch/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "composite": true + }, + "exclude": ["src/ui"] +} diff --git a/js/watch/tsconfig.json b/js/watch/tsconfig.json index 7089f27a3..eec8246c2 100644 --- a/js/watch/tsconfig.json +++ b/js/watch/tsconfig.json @@ -3,8 +3,8 @@ "compilerOptions": { "rootDir": ".", "outDir": "dist", - "composite": true + "jsx": "preserve", + "jsxImportSource": "solid-js" }, - "include": ["src"], - "exclude": ["src/ui"] + "include": ["src"] } From 978d2188a9b647a7394eb36cc4f048c99a9c2eb0 Mon Sep 17 00:00:00 2001 From: James Reetzke Date: Thu, 12 Feb 2026 11:53:43 -0500 Subject: [PATCH 10/17] chore: remove restructuring plan document --- js/docs/restructure-plan.md | 300 ------------------------------------ 1 file changed, 300 deletions(-) delete mode 100644 js/docs/restructure-plan.md diff --git a/js/docs/restructure-plan.md b/js/docs/restructure-plan.md deleted file mode 100644 index 08ac87ffb..000000000 --- a/js/docs/restructure-plan.md +++ /dev/null @@ -1,300 +0,0 @@ -# Restructuring Plan: `@moq/hang` → Separate Watch/Publish Packages - -## Background - -The goal is to split watch and publish functionality out of `@moq/hang` so that `@moq/hang` is strictly the core protocol + container logic. Watch and publish move into their own packages (`@moq/watch`, `@moq/publish`), each co-locating their UI web component (e.g. `@moq/watch/ui`). Shared UI primitives (button, icons, stats, CSS variables) go into `@moq/ui-core`. - -## Current Structure - -| Package | Contents | Build | -|---|---|---| -| `@moq/hang` | catalog, container, watch, publish, support, util | `tsc` | -| `@moq/hang-ui` | watch UI, publish UI, shared UI (button, icons, stats, CSS) | vite lib mode | -| `@moq/hang-demo` | demo app consuming both | vite | - -### Key dependencies - -- `hang/src/watch/` and `hang/src/publish/` both import `../catalog` (broadcast, preview, user) and depend on `@moq/lite` + `@moq/signals`. -- `hang-ui/src/watch/` and `hang-ui/src/publish/` both import from `../shared/` (button, icons, stats, CSS variables). -- `hang-ui` is built with vite library mode and uses SolidJS web components. -- AudioWorklets exist in `publish/audio/capture-worklet.ts`. - -## Target Structure - -| Package | Contents | Build | -|---|---|---| -| `@moq/hang` | catalog, container, support, util only | tsc (or vite) | -| `@moq/watch` | watch logic + `@moq/watch/ui` web component | vite lib mode | -| `@moq/publish` | publish logic + `@moq/publish/ui` web component | vite lib mode | -| `@moq/ui-core` | shared UI: button, icons, stats, CSS variables | vite lib mode | -| `@moq/hang-demo` | updated demo | vite | - -### Final dependency graph - -``` -@moq/lite ←─────────────────────────────┐ -@moq/signals ←───────────────────────┐ │ -@moq/hang (catalog, container) ←──┐ │ │ -@moq/ui-core (button, icons, etc) │ │ │ - │ │ │ -@moq/watch ───────────────────┴──┴────┤ - └─ /ui (imports @moq/ui-core) │ -@moq/publish ───────────────────┴──┴────┘ - └─ /ui (imports @moq/ui-core) -``` - ---- - -## Cross-Cutting Concerns - -These items span multiple milestones and should be addressed as each milestone is completed: - -### Root `package.json` workspaces -The root `package.json` `workspaces` array currently lists `js/hang`, `js/hang-ui`, and `js/hang-demo`. As new packages are created and old ones removed: -- **M1:** Add `js/ui-core` -- **M2:** Add `js/watch` -- **M3:** Add `js/publish` -- **M6:** Remove `js/hang-ui` - -### VitePress sidebar (`doc/.vitepress/config.ts`) -The sidebar currently nests Watch and Publish under `@moq/hang` and has a separate `@moq/hang-ui` entry. This needs restructuring: -- **M2–M3:** Move Watch/Publish to top-level sidebar entries (`@moq/watch`, `@moq/publish`) -- **M1:** Add `@moq/ui-core` sidebar entry -- **M6:** Remove `@moq/hang-ui` sidebar entry - -### `CLAUDE.md` -References `hang-ui/` in the project structure and architecture layers. Update: -- **M1:** Add `ui-core/` to the project structure -- **M2–M3:** Add `watch/` and `publish/` to the project structure -- **M6:** Remove `hang-ui/` from the project structure, update architecture description - -### Root `README.md` -Package table includes `@moq/hang-ui`. Update: -- **M1:** Add `@moq/ui-core` row -- **M2–M3:** Add `@moq/watch` and `@moq/publish` rows, update `@moq/hang` description -- **M6:** Remove `@moq/hang-ui` row - -### `doc/index.md` -References `@moq/hang-ui` in the highlights list. Update alongside M6. - ---- - -## Milestones - -### Milestone 1: Create `@moq/ui-core` package - -**Why first:** Prerequisite for Milestones 4 & 5. Extracting shared UI early establishes the `@moq/ui-core` contract before anything else moves. - -**Scope:** -- Create `js/ui-core/` with `package.json` (`@moq/ui-core`), `vite.config.ts` (library mode), `tsconfig.json`. -- Move `hang-ui/src/shared/` contents into `ui-core/src/` (button, icons + SVGs, stats, `variables.css`, `flex.css`). -- The icon component uses `?raw` SVG imports — vite handles this natively. -- Update `hang-ui/src/watch/` and `hang-ui/src/publish/` to import from `@moq/ui-core` instead of `../../shared/`. -- Verify `hang-ui` still builds and `hang-demo` still works. - -**README updates:** -- Create a new `js/ui-core/README.md` documenting the shared components (button, icon, stats), CSS variables, and usage. -- Move the stats component README (`hang-ui/src/shared/components/stats/README.md`) into `ui-core` alongside the component. -- Update `hang-ui/README.md` "shared/" section under "Project Structure" and "Module Overview" to note that shared components now come from `@moq/ui-core`. - -**Other updates:** -- Add `js/ui-core` to root `package.json` workspaces. -- Add `@moq/ui-core` entry to `doc/.vitepress/config.ts` sidebar. -- Add `ui-core/` to `CLAUDE.md` project structure. -- Add `@moq/ui-core` row to root `README.md` package table. - -**Exit criteria:** `@moq/ui-core` builds independently; `@moq/hang-ui` builds and works with `@moq/ui-core` as a dependency. - ---- - -### Milestone 2: Create `@moq/watch` package (logic only) - -**Scope:** -- Create `js/watch/` with `package.json`, `vite.config.ts`, `tsconfig.json`. -- Move `hang/src/watch/` → `watch/src/`. -- Change 3 internal imports (`../catalog` in `broadcast.ts`, `preview.ts`, `user.ts`) to `@moq/hang/catalog`. -- Dependencies: `@moq/hang` (for catalog), `@moq/lite`, `@moq/signals`, plus watch-specific deps from hang's `package.json` (e.g. `@kixelated/libavjs-webcodecs-polyfill`, `@libav.js/variant-opus-af`). -- Remove `./watch` and `./watch/element` exports from `hang/package.json`. -- Remove `export * as Watch from "./watch"` from `hang/src/index.ts`. - -**README updates:** -- Create a new `js/watch/README.md` documenting the `@moq/watch` package: installation, JS API, and web component usage (`` element and attributes). -- Pull the relevant content from `hang/README.md` sections: `` attributes, the watch portion of the JS API example, and tree-shaking notes. -- Update `hang/README.md`: - - Remove the `` section and its code examples. - - Remove watch-related JS API examples (`Hang.Watch.Broadcast`, etc.). - - Add a note pointing users to `@moq/watch` for watch functionality. - -**Doc site updates:** -- Move `doc/js/@moq/hang/watch.md` → `doc/js/@moq/watch.md` (or similar), update all import paths from `@moq/hang/watch` to `@moq/watch`. -- Update `doc/js/@moq/hang/index.md` to remove watch sections and link to the new `@moq/watch` page. -- Update `doc/js/index.md` "Quick Start" examples to use `@moq/watch` imports. -- Update `doc/js/env/web.md` `` section: change imports from `@moq/hang/watch/element` to `@moq/watch/element`. -- Update `doc/js/@moq/signals.md` example import. -- Update `doc/.vitepress/config.ts` sidebar: move Watch out from under `@moq/hang` to a top-level `@moq/watch` entry. - -**Other updates:** -- Add `js/watch` to root `package.json` workspaces. -- Add `watch/` to `CLAUDE.md` project structure. -- Add `@moq/watch` row to root `README.md` package table. - -**Exit criteria:** `@moq/watch` builds. `@moq/hang` builds without watch. - -> **Note:** Milestones 2 and 3 can be done in parallel. - ---- - -### Milestone 3: Create `@moq/publish` package (logic only) - -**Scope:** -- Create `js/publish/` with `package.json`, `vite.config.ts`, `tsconfig.json`. -- Move `hang/src/publish/` → `publish/src/`. -- Change 3 internal imports (`../catalog` in `broadcast.ts`, `preview.ts`, `user.ts`) to `@moq/hang/catalog`. -- **AudioWorklet handling:** `publish/audio/capture-worklet.ts` currently uses a vite-specific import. For now, inline it using the `toString()` pattern or a vite/rollup plugin. This is the trickiest part of this milestone. -- Dependencies: `@moq/hang`, `@moq/lite`, `@moq/signals`, `comlink`, `async-mutex`. -- Remove `./publish` and `./publish/element` exports from `hang/package.json`. -- Remove `export * as Publish from "./publish"` from `hang/src/index.ts`. - -**README updates:** -- Create a new `js/publish/README.md` documenting the `@moq/publish` package: installation, JS API, and web component usage (`` element and attributes). -- Pull the relevant content from `hang/README.md` sections: `` attributes, the publish portion of the JS API example, and tree-shaking notes. -- Update `hang/README.md`: - - Remove the `` section and its code examples. - - Remove publish-related JS API examples (`Hang.Publish.Broadcast`, etc.). - - Add a note pointing users to `@moq/publish` for publish functionality. - - After both M2 and M3, the "Web Components" and "Javascript API" sections should reference only `` or be replaced with a high-level overview pointing to the new packages. - -**Doc site updates:** -- Move `doc/js/@moq/hang/publish.md` → `doc/js/@moq/publish.md` (or similar), update all import paths from `@moq/hang/publish` to `@moq/publish`. -- Update `doc/js/@moq/hang/index.md` to remove publish sections and link to the new `@moq/publish` page. -- Update `doc/js/index.md` "Quick Start" examples to use `@moq/publish` imports. -- Update `doc/js/env/web.md` `` section: change imports from `@moq/hang/publish/element` to `@moq/publish/element`. -- Update `doc/.vitepress/config.ts` sidebar: move Publish out from under `@moq/hang` to a top-level `@moq/publish` entry. - -**Other updates:** -- Add `js/publish` to root `package.json` workspaces. -- Add `publish/` to `CLAUDE.md` project structure. -- Add `@moq/publish` row to root `README.md` package table. - -**Exit criteria:** `@moq/publish` builds with worklet inlined. `@moq/hang` builds without publish. - -> **Note:** Milestones 2 and 3 can be done in parallel. - ---- - -### Milestone 4: Move watch UI into `@moq/watch/ui` - -**Scope:** -- Move `hang-ui/src/watch/` → `watch/src/ui/`. -- Add SolidJS + `solid-element` + `vite-plugin-solid` as dev deps of `@moq/watch`. -- Update vite config to have two entry points: - - `watch/index` → `src/index.ts` (logic) - - `watch/ui` → `src/ui/index.tsx` (web component) -- Update UI imports: `@moq/hang/watch/element` → local `../element`, shared components → `@moq/ui-core`. -- Add `@moq/ui-core` as a dependency. -- Configure `rollupOptions.external` to externalize `@moq/hang`, `@moq/lite`, `@moq/signals`, `@moq/ui-core`. -- Export `./ui` in `package.json`. - -**README updates:** -- Update `js/watch/README.md` to add a "UI" section documenting the `@moq/watch/ui` entry point, how to use ``, and its dependency on `@moq/ui-core`. -- Update `hang-ui/README.md` to remove the `watch/` section from "Project Structure" and "Module Overview", noting it has moved to `@moq/watch/ui`. - -**Doc site updates:** -- Update `doc/js/@moq/watch.md` SolidJS integration section: change `@moq/hang-ui/watch` to `@moq/watch/ui`. - -**Exit criteria:** `@moq/watch` builds both logic and UI. The `hang-watch-ui` custom element registers and functions. - -> **Note:** Milestones 4 and 5 can be done in parallel. - ---- - -### Milestone 5: Move publish UI into `@moq/publish/ui` - -**Scope:** Mirror of Milestone 4 for publish: -- Move `hang-ui/src/publish/` → `publish/src/ui/`. -- Same vite/SolidJS setup as watch. -- Two entry points: `@moq/publish` (logic) and `@moq/publish/ui` (web component). -- Update imports similarly. - -**README updates:** -- Update `js/publish/README.md` to add a "UI" section documenting the `@moq/publish/ui` entry point, how to use ``, and its dependency on `@moq/ui-core`. -- Update `hang-ui/README.md` to remove the `publish/` section from "Project Structure" and "Module Overview", noting it has moved to `@moq/publish/ui`. - -**Doc site updates:** -- Update `doc/js/@moq/publish.md` SolidJS integration section: change `@moq/hang-ui/publish` to `@moq/publish/ui`. - -**Exit criteria:** `@moq/publish` builds both logic and UI. The `hang-publish-ui` custom element registers and functions. - -> **Note:** Milestones 4 and 5 can be done in parallel. - ---- - -### Milestone 6: Update `@moq/hang-demo` and remove `@moq/hang-ui` - -**Scope:** -- Update `hang-demo/package.json`: replace `@moq/hang-ui` with `@moq/watch`, `@moq/publish` (and `@moq/ui-core` if used directly). -- Update all imports in `hang-demo`: - - `@moq/hang-ui/watch` → `@moq/watch/ui` - - `@moq/hang-ui/publish` → `@moq/publish/ui` - - `@moq/hang/watch` → `@moq/watch` - - `@moq/hang/publish` → `@moq/publish` -- Delete `js/hang-ui/` entirely. -- Full end-to-end test of the demo app. - -**README updates:** -- Update `hang-demo/README.md` if it gains any references to specific packages (currently minimal). -- Delete `hang-ui/README.md` along with the `hang-ui` package. - -**Doc site updates:** -- Delete `doc/js/@moq/hang-ui.md`. -- Remove `@moq/hang-ui` sidebar entry from `doc/.vitepress/config.ts`. -- Remove `js/hang-ui` from root `package.json` workspaces. -- Update `doc/index.md` to replace `@moq/hang-ui` reference with `@moq/watch/ui` and `@moq/publish/ui`. -- Update `doc/js/index.md` to remove `@moq/hang-ui` section and install command; replace with `@moq/watch`, `@moq/publish`, `@moq/ui-core`. -- Update `doc/js/env/web.md` SolidJS section to reference `@moq/watch/ui` and `@moq/publish/ui` instead of `@moq/hang-ui`. -- Remove `hang-ui/` from `CLAUDE.md` project structure, update architecture description. -- Remove `@moq/hang-ui` row from root `README.md` package table. - -**Exit criteria:** `hang-demo` runs correctly with the new packages. `hang-ui` is gone. - ---- - -### Milestone 7: Clean up `@moq/hang` - -**Scope:** -- Delete `hang/src/watch/` and `hang/src/publish/` directories. -- Remove now-unused dependencies from `hang/package.json` (e.g. `@kixelated/libavjs-webcodecs-polyfill`, `@libav.js/variant-opus-af`, `comlink`, `async-mutex` — if only used by watch/publish). -- Remove `sideEffects` entries for watch/publish elements. -- Clean up `hang/src/index.ts` (should only export Catalog, Container, Support, and re-exports of Moq/Signals). -- Consider switching `@moq/hang` build from `tsc` to vite lib mode for consistency and jsdelivr support. -- Full build + test pass across all packages. - -**README updates:** -- Final rewrite of `hang/README.md`: - - Update the package description to reflect its new scope (catalog, container, support only). - - Remove the "Web Components" section entirely (or keep only ``). - - Replace the "Javascript API" section with catalog/container examples. - - Update the "Features" list to reflect core-only functionality. - - Add a "Related Packages" section linking to `@moq/watch`, `@moq/publish`, and `@moq/ui-core`. - -**Doc site updates:** -- Rewrite `doc/js/@moq/hang/index.md` to reflect core-only scope (catalog, container, support). Add links to `@moq/watch` and `@moq/publish` pages. -- Update `doc/js/index.md` description of `@moq/hang` in the "Core Libraries" section. -- Update `doc/js/env/web.md` production notes about Vite-only support (now applies to `@moq/watch`/`@moq/publish` instead of `@moq/hang`). -- Update `CLAUDE.md` architecture layer description for `hang`. -- Update root `README.md` description of `@moq/hang` in the package table. - -**Exit criteria:** `@moq/hang` is lean (catalog + container + support only). All packages build and tests pass. - ---- - -### Milestone 8: (Future) CDN / jsdelivr support - -**Scope:** -- Investigate vite/rollup plugin for proper AudioWorklet bundling (avoiding `toString()` hack). -- Set up `public/` directories in `@moq/watch` and `@moq/publish` for runtime-loadable assets. -- Add configurable `basePath` similar to [Shoelace](https://shoelace.style/getting-started/installation#setting-the-base-path). -- Test serving all packages via jsdelivr. -- Document the CDN usage pattern for consumers. - -**Exit criteria:** Packages are usable via `