From 75a0c5350ba4c66abdbe01235b15f845211a9d63 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Sun, 5 Jul 2026 13:26:29 -0700 Subject: [PATCH 1/2] update moq packages --- api/package.json | 2 +- app/package.json | 11 +- app/src/components/meme-selector.tsx | 4 +- app/src/components/profile.tsx | 4 +- app/src/controls.tsx | 3 +- app/src/room/broadcast.ts | 4 +- app/src/room/fake.ts | 7 +- app/src/room/index.ts | 9 +- app/src/room/local.ts | 9 +- app/src/room/locator.ts | 6 +- app/src/room/metadata.ts | 334 +++++++++++++++++++++++++++ app/src/room/preview/member.ts | 51 +++- app/src/room/watch.ts | 45 +--- bun.lock | 45 ++-- 14 files changed, 449 insertions(+), 85 deletions(-) create mode 100644 app/src/room/metadata.ts diff --git a/api/package.json b/api/package.json index 0d4cdbb2..04e6248d 100644 --- a/api/package.json +++ b/api/package.json @@ -16,7 +16,7 @@ }, "dependencies": { "@hono/zod-validator": "^0.7.2", - "@moq/token": "^0.1.2", + "@moq/token": "^0.2.3", "drizzle-orm": "^0.44.5", "hono": "^4.9.4", "jose": "^6.0.13", diff --git a/app/package.json b/app/package.json index 29aeb6f5..dc8148a8 100644 --- a/app/package.json +++ b/app/package.json @@ -17,11 +17,12 @@ "@fontsource-variable/montserrat": "^5.2.6", "@hang/api": "workspace:*", "@huggingface/transformers": "^4.0.0", - "@moq/hang": "^0.2.1", - "@moq/lite": "^0.1.7", - "@moq/publish": "^0.2.3", - "@moq/signals": "^0.1.4", - "@moq/watch": "^0.2.3", + "@moq/hang": "^0.2.12", + "@moq/json": "^0.1.1", + "@moq/lite": "^0.3.0", + "@moq/publish": "^0.2.16", + "@moq/signals": "^0.1.10", + "@moq/watch": "^0.2.18", "@solidjs/router": "^0.15.3", "@tailwindcss/vite": "^4.1.12", "@tauri-apps/api": "^2.8.0", diff --git a/app/src/components/meme-selector.tsx b/app/src/components/meme-selector.tsx index bebd6a12..34c3ae5b 100644 --- a/app/src/components/meme-selector.tsx +++ b/app/src/components/meme-selector.tsx @@ -1,14 +1,14 @@ -import type * as Publish from "@moq/publish"; import solid from "@moq/signals/solid"; import { createEffect, createSignal, For, onCleanup, onMount, type Setter, Show } from "solid-js"; import type { JSX } from "solid-js/jsx-runtime"; import * as Meme from "../room/meme"; +import type { HangPublishBroadcast } from "../room/metadata"; import Settings from "../settings"; export type Tab = "emoji" | "audio" | "video"; export type MemeSelectorProps = { - broadcast: Publish.Broadcast; + broadcast: HangPublishBroadcast; chatInput: HTMLInputElement | undefined; chatMessage: string; setChatMessage: Setter; diff --git a/app/src/components/profile.tsx b/app/src/components/profile.tsx index 55e9a627..bdf2141e 100644 --- a/app/src/components/profile.tsx +++ b/app/src/components/profile.tsx @@ -1,4 +1,3 @@ -import * as Publish from "@moq/publish"; import { Effect } from "@moq/signals"; import solid from "@moq/signals/solid"; import { createSignal, JSX, onCleanup, Show } from "solid-js"; @@ -6,6 +5,7 @@ import * as Api from "../api"; import { Camera, Microphone } from "../controls"; import { Canvas } from "../room/canvas"; import { Local } from "../room/local"; +import type { HangPublishBroadcast } from "../room/metadata"; import { Sound } from "../room/sound"; import { Space } from "../room/space"; import Settings from "../settings"; @@ -122,7 +122,7 @@ class LocalPreview { signals = new Effect(); - constructor(element: HTMLCanvasElement, camera: Publish.Broadcast) { + constructor(element: HTMLCanvasElement, camera: HangPublishBroadcast) { // Create a minimal canvas without the background effects this.canvas = new Canvas(element); diff --git a/app/src/controls.tsx b/app/src/controls.tsx index b60abaa0..9d4bace1 100644 --- a/app/src/controls.tsx +++ b/app/src/controls.tsx @@ -19,6 +19,7 @@ import { Tutorial } from "./components/tutorial"; import type { Room } from "./room"; import type { Canvas } from "./room/canvas"; import { Local } from "./room/local"; +import type { HangPublishBroadcast } from "./room/metadata"; import { Sound } from "./room/sound"; import Settings, { Modal } from "./settings"; import * as Tauri from "./tauri"; @@ -738,7 +739,7 @@ export function Visualize(props: { audio: Publish.Audio.Encoder }): JSX.Element ); } -function Chat(props: { broadcast: Publish.Broadcast; room: Room }): JSX.Element { +function Chat(props: { broadcast: HangPublishBroadcast; room: Room }): JSX.Element { const [input, setInput] = createSignal(undefined); const [message, setMessage] = createSignal(""); const [showMemeSelector, setShowMemeSelector] = createSignal(false); diff --git a/app/src/room/broadcast.ts b/app/src/room/broadcast.ts index 3874bbae..172ac7d6 100644 --- a/app/src/room/broadcast.ts +++ b/app/src/room/broadcast.ts @@ -1,4 +1,3 @@ -import * as Publish from "@moq/publish"; import { Effect, Signal } from "@moq/signals"; import { Audio } from "./audio"; import { Canvas } from "./canvas"; @@ -8,12 +7,13 @@ import { FakeBroadcast } from "./fake"; import { Bounds, Vector } from "./geometry"; import { MeshBuffer } from "./gl/mesh"; import * as Meme from "./meme"; +import type { HangPublishBroadcast } from "./metadata"; import { Name } from "./name"; import { Sound } from "./sound"; import { Video } from "./video"; import { WatchBroadcast } from "./watch"; -export type BroadcastSource = WatchBroadcast | Publish.Broadcast | FakeBroadcast; +export type BroadcastSource = WatchBroadcast | HangPublishBroadcast | FakeBroadcast; export type ChatMessage = { audio?: HTMLAudioElement; diff --git a/app/src/room/fake.ts b/app/src/room/fake.ts index 902a6f17..0e59e886 100644 --- a/app/src/room/fake.ts +++ b/app/src/room/fake.ts @@ -1,12 +1,13 @@ import { Catalog } from "@moq/hang"; import { Effect, Signal } from "@moq/signals"; import { Canvas } from "./canvas"; +import type { Position, User } from "./metadata"; import { Sound } from "./sound"; import { Space } from "./space"; export type FakeBroadcastProps = { - position?: Catalog.Position; - user?: Catalog.User; + position?: Position; + user?: User; }; export class FakeBroadcast { @@ -16,7 +17,7 @@ export class FakeBroadcast { location = { window: { - position: new Signal(undefined), + position: new Signal(undefined), handle: new Signal(undefined), }, }; diff --git a/app/src/room/index.ts b/app/src/room/index.ts index 5dac6d45..6ec55132 100644 --- a/app/src/room/index.ts +++ b/app/src/room/index.ts @@ -5,6 +5,7 @@ import { Broadcast } from "./broadcast"; import type { Canvas } from "./canvas"; import { Local } from "./local"; import { Locator } from "./locator"; +import type { HangPublishBroadcast } from "./metadata"; import { Sound } from "./sound"; import { Space } from "./space"; import { WatchBroadcast } from "./watch"; @@ -28,8 +29,8 @@ export class Room { // The physics space for the room. space: Space; - #cameraBroadcast = new Signal | undefined>(undefined); - #shareBroadcast = new Signal | undefined>(undefined); + #cameraBroadcast = new Signal | undefined>(undefined); + #shareBroadcast = new Signal | undefined>(undefined); #signals = new Effect(); @@ -108,7 +109,7 @@ export class Room { if (update.path === this.local.camera.name.peek()) { if (update.active) { const broadcast = this.space.add(update.path, this.local.camera); - this.#cameraBroadcast.set(broadcast as Broadcast); + this.#cameraBroadcast.set(broadcast as Broadcast); } else { this.space.remove(update.path); this.#cameraBroadcast.set(undefined); @@ -120,7 +121,7 @@ export class Room { if (update.path === this.local.share.name.peek()) { if (update.active) { const broadcast = this.space.add(update.path, this.local.share); - this.#shareBroadcast.set(broadcast as Broadcast); + this.#shareBroadcast.set(broadcast as Broadcast); } else { this.space.remove(update.path); this.#shareBroadcast.set(undefined); diff --git a/app/src/room/local.ts b/app/src/room/local.ts index 6e81336b..c9551436 100644 --- a/app/src/room/local.ts +++ b/app/src/room/local.ts @@ -2,6 +2,7 @@ import type * as Moq from "@moq/lite"; import * as Publish from "@moq/publish"; import { Effect, Signal } from "@moq/signals"; import Settings from "../settings"; +import { createPublishBroadcast, type HangPublishBroadcast } from "./metadata"; export interface LocalProps { connection?: Signal | Moq.Connection.Established; @@ -15,11 +16,11 @@ export interface LocalProps { export class Local { connection: Signal; - camera: Publish.Broadcast; + camera: HangPublishBroadcast; microphone: Publish.Source.Microphone; webcam: Publish.Source.Camera; - share: Publish.Broadcast; + share: HangPublishBroadcast; screen: Publish.Source.Screen; // Name and avatar signals that can be overridden @@ -65,7 +66,7 @@ export class Local { this.#signals.cleanup(() => this.microphone.close()); // Create the camera broadcast - this.camera = new Publish.Broadcast({ + this.camera = createPublishBroadcast({ enabled: Local.join, connection: this.connection, user: { @@ -139,7 +140,7 @@ export class Local { this.#signals.cleanup(() => this.screen.close()); // Create the screen broadcast - this.share = new Publish.Broadcast({ + this.share = createPublishBroadcast({ connection: this.connection, audio: { enabled: this.screen.enabled, diff --git a/app/src/room/locator.ts b/app/src/room/locator.ts index 579124a7..002847be 100644 --- a/app/src/room/locator.ts +++ b/app/src/room/locator.ts @@ -1,16 +1,16 @@ -import type * as Publish from "@moq/publish"; import { Effect, Signal } from "@moq/signals"; import * as DOM from "@moq/signals/dom"; import type { Broadcast } from "./broadcast"; import { Bounds, Vector } from "./geometry"; +import type { HangPublishBroadcast } from "./metadata"; export class Locator { - broadcast: Broadcast; + broadcast: Broadcast; signals = new Effect(); #visible = new Signal(true); - constructor(broadcast: Broadcast) { + constructor(broadcast: Broadcast) { this.broadcast = broadcast; this.signals.effect(this.#render.bind(this)); diff --git a/app/src/room/metadata.ts b/app/src/room/metadata.ts new file mode 100644 index 00000000..ff55fac4 --- /dev/null +++ b/app/src/room/metadata.ts @@ -0,0 +1,334 @@ +import type { Root as CatalogRoot } from "@moq/hang/catalog"; +import * as Json from "@moq/json"; +import type * as Moq from "@moq/lite"; +import * as Publish from "@moq/publish"; +import { Effect, type Getter, Signal } from "@moq/signals"; +import * as Watch from "@moq/watch"; + +const TRACKS = { + user: "hang/user.json", + location: "hang/location.json", + chat: "hang/chat.json", + preview: "hang/preview.json", +} as const; + +const HANG_PRIORITY = 90; + +export type Position = { + x?: number; + y?: number; + z?: number; + s?: number; +}; + +export type User = { + id?: string; + name?: string; + avatar?: string; + color?: string; +}; + +export type LocationValue = { + window?: { + position?: Position; + handle?: string; + }; + peers?: { + positions?: Record; + }; +}; + +export type ChatValue = { + message?: string; + typing?: boolean; +}; + +export type PreviewInfo = { + audio?: boolean; + video?: boolean; + chat?: boolean; + typing?: boolean; + screen?: boolean; + name?: string; + avatar?: string; +}; + +export type PreviewValue = { + info?: PreviewInfo; +}; + +type TrackRef = { + track: string; +}; + +type HangCatalog = { + user?: TrackRef; + location?: TrackRef; + chat?: TrackRef; + preview?: TrackRef; +}; + +type ExtendedCatalog = CatalogRoot & { + hang?: HangCatalog; +}; + +export type MetadataProps = { + user?: { + enabled?: boolean | Signal; + id?: string | Signal; + name?: string | Signal; + avatar?: string | Signal; + color?: string | Signal; + }; + location?: { + window?: { + enabled?: boolean | Signal; + position?: Position | Signal; + handle?: string | Signal; + }; + peers?: { + enabled?: boolean | Signal; + positions?: Record | Signal | undefined>; + }; + }; + chat?: { + message?: { + enabled?: boolean | Signal; + latest?: string | Signal; + }; + typing?: { + enabled?: boolean | Signal; + active?: boolean | Signal; + }; + }; + preview?: { + enabled?: boolean | Signal; + info?: PreviewInfo | Signal; + }; +}; + +export type Metadata = { + user: { + id: Signal; + name: Signal; + avatar: Signal; + color: Signal; + }; + location: { + window: { + enabled: Signal; + position: Signal; + handle: Signal; + }; + peers: { + enabled: Signal; + positions: Signal | undefined>; + }; + }; + chat: { + message: { + enabled: Signal; + latest: Signal; + }; + typing: { + enabled: Signal; + active: Signal; + }; + }; + preview: { + enabled: Signal; + info: Signal; + }; +}; + +export type HangPublishBroadcast = Publish.Broadcast & Metadata; + +function fromOptional(value: T | Signal | undefined): Signal { + return Signal.from(value); +} + +function fromBoolean(value: boolean | Signal | undefined, fallback = false): Signal { + return Signal.from(value ?? fallback); +} + +function createMetadata(props?: MetadataProps): Metadata { + return { + user: { + id: fromOptional(props?.user?.id), + name: fromOptional(props?.user?.name), + avatar: fromOptional(props?.user?.avatar), + color: fromOptional(props?.user?.color), + }, + location: { + window: { + enabled: fromBoolean(props?.location?.window?.enabled), + position: fromOptional(props?.location?.window?.position), + handle: fromOptional(props?.location?.window?.handle), + }, + peers: { + enabled: fromBoolean(props?.location?.peers?.enabled), + positions: fromOptional(props?.location?.peers?.positions), + }, + }, + chat: { + message: { + enabled: fromBoolean(props?.chat?.message?.enabled), + latest: fromOptional(props?.chat?.message?.latest), + }, + typing: { + enabled: fromBoolean(props?.chat?.typing?.enabled), + active: fromOptional(props?.chat?.typing?.active), + }, + }, + preview: { + enabled: fromBoolean(props?.preview?.enabled), + info: Signal.from(props?.preview?.info ?? {}), + }, + }; +} + +export function createPublishBroadcast(props?: Publish.BroadcastProps & MetadataProps): HangPublishBroadcast { + const broadcast = new Publish.Broadcast(props); + const metadata = createMetadata(props); + const extended = Object.assign(broadcast, metadata) as HangPublishBroadcast; + + extended.location.window.handle.set( + extended.location.window.handle.peek() ?? Math.random().toString(36).substring(2, 15), + ); + + const user = new Json.Producer({ initial: {} }); + const location = new Json.Producer({ initial: {} }); + const chat = new Json.Producer({ initial: {} }); + const preview = new Json.Producer({ initial: {} }); + + const unpublish = [ + extended.publishTrack(TRACKS.user, (track, effect) => user.serve(track, effect)), + extended.publishTrack(TRACKS.location, (track, effect) => location.serve(track, effect)), + extended.publishTrack(TRACKS.chat, (track, effect) => chat.serve(track, effect)), + extended.publishTrack(TRACKS.preview, (track, effect) => preview.serve(track, effect)), + ]; + + extended.catalog.mutate((catalog: ExtendedCatalog) => { + catalog.hang = { + user: { track: TRACKS.user }, + location: { track: TRACKS.location }, + chat: { track: TRACKS.chat }, + preview: { track: TRACKS.preview }, + }; + }); + + extended.signals.effect((effect) => { + user.update({ + id: effect.get(extended.user.id), + name: effect.get(extended.user.name), + avatar: effect.get(extended.user.avatar), + color: effect.get(extended.user.color), + }); + }); + + extended.signals.effect((effect) => { + location.update({ + window: { + position: effect.get(extended.location.window.position), + handle: effect.get(extended.location.window.handle), + }, + peers: { + positions: effect.get(extended.location.peers.positions), + }, + }); + }); + + extended.signals.effect((effect) => { + chat.update({ + message: effect.get(extended.chat.message.latest), + typing: effect.get(extended.chat.typing.active), + }); + }); + + extended.signals.effect((effect) => { + preview.update({ + info: effect.get(extended.preview.info), + }); + }); + + extended.signals.cleanup(() => { + for (const stop of unpublish) stop(); + user.finish(); + location.finish(); + chat.finish(); + preview.finish(); + }); + + return extended; +} + +export function createWatchMetadata( + broadcast: Watch.Broadcast, + props?: MetadataProps, +): Metadata & { close: () => void } { + const metadata = createMetadata(props); + const signals = new Effect(); + + signals.effect((effect) => { + const catalog = effect.get(broadcast.catalog) as ExtendedCatalog | undefined; + const hang = catalog?.hang; + if (!hang) return; + + if (hang.user) { + subscribeJson(broadcast, hang.user.track, effect, (value) => { + metadata.user.id.set(value.id); + metadata.user.name.set(value.name); + metadata.user.avatar.set(value.avatar); + metadata.user.color.set(value.color); + }); + } + + if (hang.location) { + subscribeJson(broadcast, hang.location.track, effect, (value) => { + metadata.location.window.position.set(value.window?.position); + metadata.location.window.handle.set(value.window?.handle); + metadata.location.peers.positions.set(value.peers?.positions); + }); + } + + if (hang.chat) { + subscribeJson(broadcast, hang.chat.track, effect, (value) => { + metadata.chat.message.latest.set(value.message); + metadata.chat.typing.active.set(value.typing); + }); + } + + if (hang.preview) { + subscribeJson(broadcast, hang.preview.track, effect, (value) => { + metadata.preview.info.set(value.info ?? {}); + }); + } + }); + + return { + ...metadata, + close: () => signals.close(), + }; +} + +function subscribeJson(broadcast: Watch.Broadcast, name: string, effect: Effect, update: (value: T) => void): void { + const unsubscribe = broadcast.subscribeTrack(name, HANG_PRIORITY, (track, trackEffect) => { + const consumer = new Json.Consumer(track); + trackEffect.spawn(async () => { + for (;;) { + const value = await Promise.race([trackEffect.cancel, consumer.next()]); + if (value === undefined) break; + + update(value); + } + }); + }); + + effect.cleanup(unsubscribe); +} + +export type BroadcastUser = Metadata["user"]; +export type BroadcastLocation = Metadata["location"]; +export type BroadcastChat = Metadata["chat"]; +export type BroadcastPreview = Metadata["preview"]; +export type BroadcastActive = Getter; diff --git a/app/src/room/preview/member.ts b/app/src/room/preview/member.ts index 2eef16ae..d23715ea 100644 --- a/app/src/room/preview/member.ts +++ b/app/src/room/preview/member.ts @@ -1,7 +1,8 @@ -import * as Hang from "@moq/hang"; +import { Catalog } from "@moq/hang"; +import * as Json from "@moq/json"; import type * as Moq from "@moq/lite"; -import * as Zod from "@moq/lite/zod"; import { Effect, Signal } from "@moq/signals"; +import type { PreviewInfo, PreviewValue } from "../metadata"; export type MemberProps = { enabled?: boolean | Signal; @@ -10,7 +11,8 @@ export type MemberProps = { export class Member { broadcast: Moq.Broadcast; enabled: Signal; - info: Signal; + info: Signal; + #previewTrack = new Signal(undefined); active = new Signal(false); @@ -19,22 +21,45 @@ export class Member { constructor(broadcast: Moq.Broadcast, props?: MemberProps) { this.broadcast = broadcast; this.enabled = Signal.from(props?.enabled ?? false); - this.info = new Signal(undefined); + this.info = new Signal(undefined); this.signals.effect((effect) => { if (!effect.get(this.enabled)) return; - // Subscribe to the preview.json track directly - const track = this.broadcast.subscribe("preview.json", 0); + const track = this.broadcast.subscribe("catalog.json", Catalog.PRIORITY.catalog); effect.cleanup(() => track.close()); effect.spawn(async () => { try { + const consumer = new Catalog.Consumer(track); for (;;) { - const frame = await Zod.read(track, Hang.Catalog.PreviewSchema); - if (!frame) break; + const catalog = await Promise.race([effect.cancel, consumer.next()]); + if (!catalog) break; - this.info.set(frame); + this.#previewTrack.set(catalog.hang?.preview?.track); + } + } finally { + this.#previewTrack.set(undefined); + this.info.set(undefined); + } + }); + }); + + this.signals.effect((effect) => { + const previewTrack = effect.get(this.#previewTrack); + if (!previewTrack) return; + + const track = this.broadcast.subscribe(previewTrack, 0); + effect.cleanup(() => track.close()); + + effect.spawn(async () => { + try { + const consumer = new Json.Consumer(track); + for (;;) { + const preview = await Promise.race([effect.cancel, consumer.next()]); + if (!preview) break; + + this.info.set(preview.info); } } finally { this.info.set(undefined); @@ -53,3 +78,11 @@ export class Member { this.broadcast.close(); } } + +type ExtendedCatalog = Catalog.Root & { + hang?: { + preview?: { + track: string; + }; + }; +}; diff --git a/app/src/room/watch.ts b/app/src/room/watch.ts index 46bfa3c8..9e06fac7 100644 --- a/app/src/room/watch.ts +++ b/app/src/room/watch.ts @@ -2,6 +2,7 @@ import { Catalog } from "@moq/hang"; import type * as Moq from "@moq/lite"; import { Effect, type Getter, Signal } from "@moq/signals"; import * as Watch from "@moq/watch"; +import { type BroadcastChat, type BroadcastLocation, createWatchMetadata } from "./metadata"; // Props for creating a WatchBroadcast export interface WatchBroadcastProps { @@ -40,19 +41,13 @@ export class WatchBroadcast { #videoDecoder: Watch.Video.Decoder; #audioSource: Watch.Audio.Source; #audioDecoder: Watch.Audio.Decoder; - #preview: Watch.Preview; - // Location (public - accessed directly) - location: Watch.Location.Root; + location: BroadcastLocation; // Chat (public - accessed directly) - chat: Watch.Chat.Chat; + chat: BroadcastChat; - // User info extracted from catalog - #userId = new Signal(undefined); - #userName = new Signal(undefined); - #userAvatar = new Signal(undefined); - #userColor = new Signal(undefined); + #metadata: ReturnType; // Video flip flag derived from catalog #flip = new Signal(false); @@ -121,23 +116,9 @@ export class WatchBroadcast { enabled: props?.audio?.enabled, }); - // Create Location - this.location = new Watch.Location.Root(this.#active, this.#catalog, props?.location); - - // Create Chat - this.chat = new Watch.Chat.Chat(this.#active, this.#catalog, props?.chat); - - // Create Preview - this.#preview = new Watch.Preview(this.#active, this.#catalog, props?.preview); - - // Extract user info from catalog - this.signals.effect((effect) => { - const catalog = effect.get(this.#catalog); - this.#userId.set(catalog?.user?.id); - this.#userName.set(catalog?.user?.name); - this.#userAvatar.set(catalog?.user?.avatar); - this.#userColor.set(catalog?.user?.color); - }); + this.#metadata = createWatchMetadata(this.#broadcast, props); + this.location = this.#metadata.location; + this.chat = this.#metadata.chat; // Derive flip from video catalog this.signals.effect((effect) => { @@ -162,10 +143,10 @@ export class WatchBroadcast { }; this.user = { - id: this.#userId, - name: this.#userName, - avatar: this.#userAvatar, - color: this.#userColor, + id: this.#metadata.user.id, + name: this.#metadata.user.name, + avatar: this.#metadata.user.avatar, + color: this.#metadata.user.color, }; } @@ -176,9 +157,7 @@ export class WatchBroadcast { this.#videoSource.close(); this.#audioDecoder.close(); this.#audioSource.close(); - this.location.close(); - this.chat.close(); - this.#preview.close(); + this.#metadata.close(); this.#broadcast.close(); } } diff --git a/bun.lock b/bun.lock index ec185dd8..bdc94f45 100644 --- a/bun.lock +++ b/bun.lock @@ -15,7 +15,7 @@ "version": "0.0.1", "dependencies": { "@hono/zod-validator": "^0.7.2", - "@moq/token": "^0.1.2", + "@moq/token": "^0.2.3", "drizzle-orm": "^0.44.5", "hono": "^4.9.4", "jose": "^6.0.13", @@ -38,11 +38,12 @@ "@fontsource-variable/montserrat": "^5.2.6", "@hang/api": "workspace:*", "@huggingface/transformers": "^4.0.0", - "@moq/hang": "^0.2.1", - "@moq/lite": "^0.1.7", - "@moq/publish": "^0.2.3", - "@moq/signals": "^0.1.4", - "@moq/watch": "^0.2.3", + "@moq/hang": "^0.2.12", + "@moq/json": "^0.1.1", + "@moq/lite": "^0.3.0", + "@moq/publish": "^0.2.16", + "@moq/signals": "^0.1.10", + "@moq/watch": "^0.2.18", "@solidjs/router": "^0.15.3", "@tailwindcss/vite": "^4.1.12", "@tauri-apps/api": "^2.8.0", @@ -318,21 +319,29 @@ "@libav.js/variant-opus-af": ["@libav.js/variant-opus-af@6.8.8", "", {}, "sha512-8KBQyA8n5goN7lyctOaPxpcx7dapOgqKh8dWW/NAcl87AgM/WoUGSex3fFc46oCtTHYrUKEm1OmZUrtkt3Q56A=="], - "@moq/hang": ["@moq/hang@0.2.1", "", { "dependencies": { "@kixelated/libavjs-webcodecs-polyfill": "^0.5.5", "@libav.js/variant-opus-af": "^6.8.8", "@moq/lite": "^0.1.7", "@moq/signals": "^0.1.4", "@svta/cml-iso-bmff": "^1.0.0-alpha.9", "zod": "^4.1.5" } }, "sha512-Mv+RUrnGjpuRW63k/+ag4SPcnv2GRjTsMqTcbJjTVCtGx8VNfplhrNKnV4kILT0DJrQZP/2mLpcI+RPWUaAHHw=="], + "@moq/flate": ["@moq/flate@0.1.0", "", { "dependencies": { "pako": "^2.2.0" } }, "sha512-TJdpmp525u70YHZgBpJ1IS2QXgYAzgZAGm6RIS3bHMMS1dAmWM6Lk6ykX0CgmbzkheDLCUh+fKqD6USSEta/7w=="], - "@moq/lite": ["@moq/lite@0.1.7", "", { "dependencies": { "@moq/qmux": "^0.0.6", "@moq/signals": "^0.1.4", "async-mutex": "^0.5.0" }, "peerDependencies": { "zod": "^4.1.0" } }, "sha512-28KiFT0l4aWxPg/dr5HRxgPcfYL7IQZsuDB+mlPX4fUH5tsYrMFXfI2LSBXDpdoOB6dcXqmgqrkTaDr2wE0IaA=="], + "@moq/hang": ["@moq/hang@0.2.12", "", { "dependencies": { "@kixelated/libavjs-webcodecs-polyfill": "^0.5.5", "@libav.js/variant-opus-af": "^6.8.8", "@moq/json": "^0.1.1", "@moq/loc": "^0.1.1", "@moq/net": "^0.1.6", "@moq/signals": "^0.1.10", "@svta/cml-iso-bmff": "^1.0.2", "zod": "^4.4.3" } }, "sha512-rVz98b0l9adlwmSQ4O0kUW610N7M6zljR6wL5rqJP5gbTetbjI3NgA/xakFldRJN1NQnnVGtltYqbt2OuDfpVg=="], - "@moq/publish": ["@moq/publish@0.2.3", "", { "dependencies": { "@moq/hang": "^0.2.0", "@moq/lite": "^0.1.5", "@moq/signals": "^0.1.3", "@moq/ui-core": "^0.1.0" } }, "sha512-FKD7nGkKK5+Vrynq3W79W7hvrJizfG1s8SPT51ct+rspS80as9CzCMRUJCzO941fYm37+ScTjHyxgpZJkdBJIQ=="], + "@moq/json": ["@moq/json@0.1.1", "", { "dependencies": { "@moq/flate": "^0.1.0", "@moq/net": "^0.1.6", "@moq/signals": "^0.1.10" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-It107HOatpHhYO78kHIiu3gZx3icmDt0qyCSkkbbr1wirQIdq0wgQv/FDAsiYI8bYhRRXbPLp43f/VW8yjRHJw=="], - "@moq/qmux": ["@moq/qmux@0.0.6", "", {}, "sha512-ISuGz05lUvf1hzHW3Aw3VnsGRJe1w9Qdog3LQ66KS+l+5mzQsPANvW8yOioEe1Z9dJO2G3sAHoGPnzwnsY9SIQ=="], + "@moq/lite": ["@moq/lite@0.3.0", "", { "dependencies": { "@moq/net": "^0.1.0" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-mMLylv5KDxYiKAis9MIJaJXWRX/NyPqwrv5/ktXLZT8QPii4I+fysi9/CEF5g7IqgDP5+WjbVIxiheBx/+wyyw=="], + + "@moq/loc": ["@moq/loc@0.1.1", "", { "dependencies": { "@moq/net": "^0.1.5" } }, "sha512-/DUgFYRmgHauxTaskPNn6tmv1UEYhuUk3bK4y67jZzAMy2N8sMZL6dyXuZvA2+n8jd/yZqy/pB+/+Tfj35JixQ=="], + + "@moq/msf": ["@moq/msf@0.1.3", "", { "dependencies": { "@moq/net": "^0.1.6", "zod": "^4.4.3" } }, "sha512-2Kxoniv7AgKdZfRMciUsEBELWAygzl8R89Dhn2t99qPIPwXn2yOlXEaOSoYVNvHIivbrSasIk09zRWUjSn2B8Q=="], + + "@moq/net": ["@moq/net@0.1.6", "", { "dependencies": { "@moq/qmux": "^0.0.6", "@moq/signals": "^0.1.10", "async-mutex": "^0.5.0" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-hRhR1erz8L9fF+i8Bg6ymg4S/ITa3pjv8/Kq5HAnwR5p6h5PqeQs/6DCS0Zd45ibpRVKv1EliRaFJlOFUV4tFA=="], - "@moq/signals": ["@moq/signals@0.1.4", "", { "peerDependencies": { "@types/react": "^19.1.8", "react": "^19.0.0", "solid-js": "^1.9.7" }, "optionalPeers": ["react", "solid-js"] }, "sha512-5LFU51B/OjprssoCe170Axc90HBeIGje/pvJjeTi9rD9SZ61sflgIN8fLICJkp1LhM4jSLouEt88fcOgPpRaIg=="], + "@moq/publish": ["@moq/publish@0.2.16", "", { "dependencies": { "@moq/hang": "^0.2.12", "@moq/json": "^0.1.1", "@moq/net": "^0.1.6", "@moq/signals": "^0.1.10" } }, "sha512-vgOCX4/VZYGzeStnwlPd6MY4hcXWAzDXSrNcjKHZdezu7hycfP++YwheVvP9c4ukfTWrqGfvdO+k8VLUTuuxSw=="], - "@moq/token": ["@moq/token@0.1.2", "", { "dependencies": { "@hexagon/base64": "^2.0.4", "commander": "^14.0.2", "jose": "^6.1.0", "zod": "^4.1.5" }, "bin": { "moq-token": "cli.js" } }, "sha512-AiQ20guZV/RNIAHGEWAl2kX9WIxHotnwiBwiCMp7NrIREAf/iS8YmOfBp4jGrOMspnQZPX0Q7zpnNJtNIUurMA=="], + "@moq/qmux": ["@moq/qmux@0.0.6", "", {}, "sha512-ISuGz05lUvf1hzHW3Aw3VnsGRJe1w9Qdog3LQ66KS+l+5mzQsPANvW8yOioEe1Z9dJO2G3sAHoGPnzwnsY9SIQ=="], + + "@moq/signals": ["@moq/signals@0.1.10", "", { "peerDependencies": { "@types/react": "^19.2.17", "react": "^19.0.0", "solid-js": "^1.9.13" }, "optionalPeers": ["@types/react", "react", "solid-js"] }, "sha512-TSnQwcaywn/gIwC3UpwZbqYnXm2Zo8KZO6Y043jPiPnbnSlFWJVZ9TVdGAvQqrXGiaNihdcioF/0IEDABsfovw=="], - "@moq/ui-core": ["@moq/ui-core@0.1.0", "", { "peerDependencies": { "@moq/signals": "^0.1.2" } }, "sha512-DJNBpUNQDyh7Tou324fbJ5/pT08UPghH3OxcVdLEo9IQeX//8NiEzJwcX7iuacR32nzgdiBThIbIpeFa60U3/g=="], + "@moq/token": ["@moq/token@0.2.3", "", { "dependencies": { "@hexagon/base64": "^2.0.4", "commander": "^14.0.2", "jose": "^6.1.0", "zod": "^4.1.5" }, "bin": { "moq-token": "cli.js" } }, "sha512-I1cGFw4DcJDRtWCIBuC8eLlN9I5LkgNE9Xk2G4+20EVawIF6bhq3z+OOjGLsfPOnoD6AAKTaImmtOBrBrzEPlA=="], - "@moq/watch": ["@moq/watch@0.2.3", "", { "dependencies": { "@moq/hang": "^0.2.0", "@moq/lite": "^0.1.5", "@moq/signals": "^0.1.3", "@moq/ui-core": "^0.1.0" } }, "sha512-niAxQdmsNO7ELYYW6XgsH3/dsdp1PHt6EBPH74sR1/bNBQtMC0soZRZjrUQatD9374+MSFL2+NpV61/m1uv1nA=="], + "@moq/watch": ["@moq/watch@0.2.18", "", { "dependencies": { "@moq/hang": "^0.2.12", "@moq/json": "^0.1.1", "@moq/msf": "^0.1.3", "@moq/net": "^0.1.6", "@moq/signals": "^0.1.10" } }, "sha512-6NfPr3qEfLG5Y1g60elEPpozzrda0kCHx+adoTUL7yAxYWs+MTARoSTWzdFLQCvF6nd1eVFkC0LaMKvxtyssvw=="], "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="], @@ -452,7 +461,7 @@ "@speed-highlight/core": ["@speed-highlight/core@1.2.7", "", {}, "sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g=="], - "@svta/cml-iso-bmff": ["@svta/cml-iso-bmff@1.0.1", "", { "peerDependencies": { "@svta/cml-utils": "1.4.0" } }, "sha512-MOhATJYQ6cVrIcoY3nj8p/vGYDpG3wjQIIhBPHNt9yjFijdwFdBNqdZbCXv3aFhRjdx5Saca5TkgNJusKhnI/w=="], + "@svta/cml-iso-bmff": ["@svta/cml-iso-bmff@1.0.2", "", { "peerDependencies": { "@svta/cml-utils": "1.5.0" } }, "sha512-c9UgY1z16zgvTEa6fS++9E9zxLuPtLJ4Dw5ebOgEDtwIw+PIhbh3URGXjv30tyDU2QQTXstI6U1q6h/Q4SkxGQ=="], "@svta/cml-utils": ["@svta/cml-utils@1.4.0", "", {}, "sha512-vNtHtv/z+9I9ysxFwNrgwxic1oceVPr8TpcpV/NA1l8Gy4phynwtOppkCIBB+PmoyKDcqE4lO85g+lfsuSTBBA=="], @@ -1184,7 +1193,7 @@ "youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="], - "zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], + "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "@babel/generator/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], @@ -1196,8 +1205,12 @@ "@jridgewell/source-map/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@moq/flate/pako": ["pako@2.2.0", "", {}, "sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w=="], + "@moq/token/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], + "@moq/token/zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], + "@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], "@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], From a8510b97f5cd98d7b9b8446ffb79e4000c1e3135 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Mon, 6 Jul 2026 13:56:20 -0700 Subject: [PATCH 2/2] fix just module defaults --- dev/justfile | 8 ++++++++ justfile | 12 ------------ native/justfile | 3 +++ 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/dev/justfile b/dev/justfile index d8a2348a..85c9262a 100644 --- a/dev/justfile +++ b/dev/justfile @@ -2,6 +2,14 @@ set fallback +default: + bun install + @just dev auth-token + bun concurrently --kill-others --names api,app,relay --prefix-colors auto \ + "just api dev" \ + "just app dev" \ + "just dev root" + # Run a localhost root relay server with authentication. root: auth-key moq-relay root.toml diff --git a/justfile b/justfile index 527f1f8f..bc1fb77b 100644 --- a/justfile +++ b/justfile @@ -48,18 +48,6 @@ deploy env="staging": just api deploy "{{env}}" just app deploy "{{env}}" -dev: - bun install - @just dev auth-token - bun concurrently --kill-others --names api,app,relay --prefix-colors auto \ - "just api dev" \ - "just app dev" \ - "just dev root" - -# Run the native app in development mode -native: - just native dev - # Run the Android build, using --open to open Android Studio android *args: just native android {{args}} diff --git a/native/justfile b/native/justfile index b15f9366..92124d18 100644 --- a/native/justfile +++ b/native/justfile @@ -2,6 +2,9 @@ set fallback +default: + just native dev + # Run the CI checks check: #!/usr/bin/env bash