Skip to content

feat(hang): generic base catalog with app-layer extensions#1654

Closed
kixelated wants to merge 2 commits into
devfrom
claude/brave-nobel-427697
Closed

feat(hang): generic base catalog with app-layer extensions#1654
kixelated wants to merge 2 commits into
devfrom
claude/brave-nobel-427697

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

Summary

Supersedes the very stale #1149. Instead of a bespoke Section<T> registry, this leans on what already shipped: the base catalog becomes minimal and applications extend it with plain language features feeding the generic @moq/json / moq_json helper. No new catalog machinery.

  • Base catalog is now just video + audio. App-specific sections are gone from @moq/hang.

  • Extensions are composition, not a registry:

    • JS: z.extend(Catalog.RootSchema, { scte35: z.optional(Scte35Schema) }) → hand the schema to @moq/json's Producer/Consumer.
    • Rust: #[serde(flatten)] base: hang::Catalog in the app's own struct → use moq_json::Producer<AppCatalog>.
    • The base catalog has no deny_unknown_fields, so an extended catalog stays readable by a plain hang consumer (it just ignores the extra sections). This is what makes e.g. SCTE-35 addable entirely in the application layer (hang.live) without touching hang.
  • Removed app sections (chat, user, preview, location, capabilities) from @moq/hang, and their feature subtrees + library exports from @moq/watch and @moq/publish. These are application concerns and move to hang.live; the deleted code remains in git history for that migration. The built-in <moq-watch>/<moq-publish> elements and demo/web never used these, so the UI is unaffected.

  • @moq/publish Broadcast extension hooks so the app layer can reuse it:

    • sections: extra catalog sections merged into the published catalog.json (only one producer may own that track, hence the hook).
    • tracks: handlers for app-defined tracks (e.g. a section's own update track), consulted for otherwise-unknown subscriptions.
    • Watch needs no hook: the app subscribes to catalog.json independently with its own extended schema.

Why not redo #1149 as written

Half of #1149 already shipped differently: the generic "typed JSON over a track with validation, publishing, subscribing" machinery it hand-rolled is now @moq/json / moq_json (snapshot + delta, schema validation). And the Rust hang::Catalog was already trimmed to video/audio. So the registry is redundant; the remaining gap was making the JS base schema open-by-extension and moving the app sections out.

Breaking changes (hence dev)

  • @moq/hang: removed ChatSchema, UserSchema, PreviewSchema, LocationSchema/PositionSchema/PeersSchema, CapabilitiesSchema; RootSchema no longer has those fields; removed unused encode/decode/fetch; PRIORITY trimmed to catalog/audio/video.
  • @moq/watch / @moq/publish: removed the Chat, Location, User, Preview exports and their implementations.
  • Catalog wire shape for a base hang broadcast is unchanged (still { video, audio }); apps that relied on hang shipping chat/location/etc. must define those sections themselves.

Cross-package sync

Change Paired update
rs/hang catalog js/hang ✅, doc/concept/layer/hang.md ✅ (new "Extensions" section)
js/hang catalog js/watch, js/publish

No rs/moq-ffi / wrapper changes: hang::Catalog's shape is unchanged, so FFI consumers are unaffected.

Test plan

  • just check — EXIT 0 (Rust clippy + JS tsc + biome + remark + cargo doc/shear/sort)
  • JS tests: signals, net, json, watch, token + new js/hang/src/catalog/root.test.ts (extension contract)
  • Rust: cargo test -p hang -p moq-mux -p moq-ffi all green, incl. new extension_roundtrip test + the Catalog doctest

🤖 Generated with Claude Code

(Written by Claude)

Make the base catalog carry only `video`/`audio` and let applications add
their own root sections (e.g. SCTE-35) without modifying hang. The mechanism
is plain composition on top of the already-generic `@moq/json`:

- JS: `z.extend(Catalog.RootSchema, { scte35: ... })`
- Rust: `#[serde(flatten)] base: hang::Catalog`

The base catalog ignores unknown sections, so an extended catalog stays
readable by a plain hang consumer.

App-specific sections (chat, user, preview, location, capabilities) and their
watch/publish implementations are removed; they belong in the application
layer (hang.live) and remain in git history for that move. `@moq/publish`'s
`Broadcast` gains a `sections` input (merged into the published catalog) and a
`tracks` map (serve app-defined tracks) so the app layer can reuse it; watch
needs no hook since the app subscribes to `catalog.json` with its own schema.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kixelated kixelated changed the base branch from dev to main June 8, 2026 17:42
@kixelated kixelated changed the base branch from main to dev June 8, 2026 17:44
Freshly published advisory failing CI repo-wide, unrelated to the catalog
change. proc-macro-error2 is pulled transitively via foundations (quiche)
in moq-native; it's a build-time-only proc-macro with no safe upgrade.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Superseded by #1658, which targets main (now that moq-json landed there via #1655) and adds the catalog producer/consumer extension mechanism on top of the same base-catalog gutting. (Written by Claude)

@kixelated kixelated closed this Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant