@lumencast/runtime 0.12.0 → 0.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/{broadcast-ydSpPUje.js → broadcast-DrifeSRm.js} +3 -3
  3. package/dist/{broadcast-ydSpPUje.js.map → broadcast-DrifeSRm.js.map} +1 -1
  4. package/dist/{control-zTsF-bHP.js → control-CdGT0wrz.js} +4 -4
  5. package/dist/{control-zTsF-bHP.js.map → control-CdGT0wrz.js.map} +1 -1
  6. package/dist/{index-ClWi5UzJ.js → index-BH-3p9mt.js} +47 -38
  7. package/dist/index-BH-3p9mt.js.map +1 -0
  8. package/dist/index.html +1 -1
  9. package/dist/lumencast.js +1 -1
  10. package/dist/render/bundle.d.ts.map +1 -1
  11. package/dist/render/bundle.js +14 -0
  12. package/dist/render/bundle.js.map +1 -1
  13. package/dist/render/primitives/capture-stream-cache.d.ts +26 -0
  14. package/dist/render/primitives/capture-stream-cache.d.ts.map +1 -0
  15. package/dist/render/primitives/capture-stream-cache.js +141 -0
  16. package/dist/render/primitives/capture-stream-cache.js.map +1 -0
  17. package/dist/render/primitives/capture.d.ts +1 -1
  18. package/dist/render/primitives/capture.d.ts.map +1 -1
  19. package/dist/render/primitives/capture.js +20 -87
  20. package/dist/render/primitives/capture.js.map +1 -1
  21. package/dist/{status-pill-DkHIOL5V.js → status-pill-0rJyg4p3.js} +2 -2
  22. package/dist/{status-pill-DkHIOL5V.js.map → status-pill-0rJyg4p3.js.map} +1 -1
  23. package/dist/{test-COpMkyms.js → test-CYmNprVS.js} +4 -4
  24. package/dist/{test-COpMkyms.js.map → test-CYmNprVS.js.map} +1 -1
  25. package/dist/{tree-Cubmxeqo.js → tree-CyxbJbsP.js} +589 -567
  26. package/dist/tree-CyxbJbsP.js.map +1 -0
  27. package/package.json +4 -4
  28. package/src/render/bundle.ts +14 -0
  29. package/src/render/primitives/capture-stream-cache.ts +164 -0
  30. package/src/render/primitives/capture.tsx +19 -93
  31. package/dist/index-ClWi5UzJ.js.map +0 -1
  32. package/dist/tree-Cubmxeqo.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-BH-3p9mt.js","sources":["../src/overlay/runtime-context.tsx","../src/app.tsx","../src/render/diagnostics.ts","../src/render/filter-clamp.ts","../src/animate/transitions.ts","../src/state/apply-delta.ts","../src/state/apply-snapshot.ts","../src/state/reserved-leaves.ts","../src/state/store.ts","../src/render/bundle.ts","../src/transport/reconnect.ts","../src/transport/ws.ts","../src/internal/validate-options.ts","../src/mount.ts","../src/render/prop-allowlist.ts","../src/webrtc/meet-viewer.ts","../src/webrtc/peer-stream-registry.ts","../src/webrtc/index.ts","../src/render/headless.tsx","../src/render/asset-resolve.ts"],"sourcesContent":["import { createContext, useContext, type ReactNode } from \"react\";\nimport type { Patch } from \"@lumencast/protocol\";\nimport type { Store } from \"../state/store\";\nimport type { RenderBundle } from \"../render/bundle\";\nimport type { ConnectionStatus } from \"../transport/ws\";\nimport type { LumencastMode } from \"../types\";\nimport type { ResolveCaptureDevice } from \"../render/primitives/capture\";\nimport type { ResolvePeerStream, SubscribePeerStream } from \"../render/primitives/media\";\n\nexport interface LumencastRuntime {\n mode: LumencastMode;\n store: Store;\n bundle: RenderBundle;\n status: ConnectionStatus;\n /** Send LSDP/1 input patches to the server. */\n sendInput: (patches: Patch[]) => void;\n /** ADR 004 §A1.3 — host-provided resolver mapping a LOGICAL `deviceRef` to a\n * physical `deviceId` for the `x-zab.capture` primitive's ACQUIRE mode.\n * Injected from `MountOptions`, NOT the bundle. Absent → default device. */\n resolveCaptureDevice?: ResolveCaptureDevice;\n /** ADR 006 #4 — host-provided resolver mapping a LOGICAL `peerLabel` to the\n * live `MediaStream` of a `meet.peer`, for the `media` primitive's LIVE mode.\n * Injected from `MountOptions` (supplied by the WebRTC viewer #3), NOT the\n * bundle. Absent → the live `media` node is a stream-less box. */\n resolvePeerStream?: ResolvePeerStream;\n /** ADR 006 #3 — reactive variant : the viewer pushes a peer's stream on\n * connect and `null` on leave, so a LIVE `media` node re-renders when its\n * peer joins mid-show. Preferred over `resolvePeerStream` when present. */\n subscribePeerStream?: SubscribePeerStream;\n}\n\nconst Ctx = createContext<LumencastRuntime | null>(null);\n\nexport function LumencastRuntimeProvider({\n value,\n children,\n}: {\n value: LumencastRuntime;\n children: ReactNode;\n}) {\n return <Ctx.Provider value={value}>{children}</Ctx.Provider>;\n}\n\nexport function useLumencastRuntime(): LumencastRuntime {\n const v = useContext(Ctx);\n if (!v) {\n throw new Error(\n \"Lumencast overlay components must be rendered inside LumencastRuntimeProvider\",\n );\n }\n return v;\n}\n\n/** Read the runtime context WITHOUT throwing when no provider is mounted.\n * Render primitives (e.g. `x-zab.capture`) may render via `<Tree>` directly —\n * embedded hosts, tooling, tests — outside `mount()`'s provider. They use this\n * to pick up mount-level host config (the capture resolver) when present and\n * fall back to defaults when not. */\nexport function useOptionalLumencastRuntime(): LumencastRuntime | null {\n return useContext(Ctx);\n}\n","// Top-level React component for a mounted Lumencast instance. Reads the runtime\n// signals (bundle / status) and dispatches to the right mode.\n//\n// Per-mode code splitting: BroadcastMode / ControlMode / TestMode live in\n// separate chunks loaded only when the corresponding mode is requested. A\n// broadcast mount never downloads the overlay or test code — the broadcast\n// chunk is the bare minimum a CEF host needs to render the scene.\n//\n// Crossfade: AnimatePresence freezes the props of an exiting child so its render\n// tree keeps using the values it held at the moment it started exiting.\n\nimport { useSignals } from \"@preact/signals-react/runtime\";\nimport type { Signal } from \"@preact/signals-react\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { lazy, Suspense } from \"react\";\nimport type { Patch } from \"@lumencast/protocol\";\nimport type { Store } from \"./state/store.js\";\nimport type { RenderBundle } from \"./render/bundle.js\";\nimport type { ConnectionStatus } from \"./transport/ws.js\";\nimport { LumencastRuntimeProvider } from \"./overlay/runtime-context.js\";\nimport type { ResolveCaptureDevice } from \"./render/primitives/capture.js\";\nimport type { ResolvePeerStream, SubscribePeerStream } from \"./render/primitives/media.js\";\nimport type { LumencastMode } from \"./types.js\";\n\nconst LazyBroadcastMode = lazy(() =>\n import(\"./modes/broadcast.js\").then((m) => ({ default: m.BroadcastMode })),\n);\nconst LazyControlMode = lazy(() =>\n import(\"./modes/control.js\").then((m) => ({ default: m.ControlMode })),\n);\nconst LazyTestMode = lazy(() => import(\"./modes/test.js\").then((m) => ({ default: m.TestMode })));\n\nexport interface LumencastAppProps {\n mode: LumencastMode;\n store: Store;\n bundleSignal: Signal<RenderBundle | null>;\n statusSignal: Signal<ConnectionStatus>;\n crossfadeKeySignal: Signal<string>;\n sendInput: (patches: Patch[]) => void;\n /** ADR 004 §A1.3 — host resolver for `x-zab.capture` ACQUIRE mode. */\n resolveCaptureDevice?: ResolveCaptureDevice;\n /** ADR 006 #4 — host resolver for the `media` primitive's LIVE mode. */\n resolvePeerStream?: ResolvePeerStream;\n /** ADR 006 #3 — reactive peer-stream channel (preferred over the resolver). */\n subscribePeerStream?: SubscribePeerStream;\n}\n\nexport function LumencastApp({\n mode,\n store,\n bundleSignal,\n statusSignal,\n crossfadeKeySignal,\n sendInput,\n resolveCaptureDevice,\n resolvePeerStream,\n subscribePeerStream,\n}: LumencastAppProps) {\n useSignals();\n\n const bundle = bundleSignal.value;\n const status = statusSignal.value;\n const trackKey = crossfadeKeySignal.value;\n if (!bundle) return null;\n\n const ModeComponent =\n mode === \"broadcast\" ? LazyBroadcastMode : mode === \"control\" ? LazyControlMode : LazyTestMode;\n\n return (\n <AnimatePresence mode=\"sync\">\n <motion.div\n key={trackKey}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.4, ease: \"easeInOut\" }}\n style={{ position: \"absolute\", inset: 0 }}\n >\n <LumencastRuntimeProvider\n value={{\n mode,\n store,\n bundle,\n status,\n sendInput,\n ...(resolveCaptureDevice !== undefined ? { resolveCaptureDevice } : {}),\n ...(resolvePeerStream !== undefined ? { resolvePeerStream } : {}),\n ...(subscribePeerStream !== undefined ? { subscribePeerStream } : {}),\n }}\n >\n <Suspense fallback={null}>\n <ModeComponent />\n </Suspense>\n </LumencastRuntimeProvider>\n </motion.div>\n </AnimatePresence>\n );\n}\n","// Anti-silent-drop diagnostics channel (ADR 001 §3.4 D4, issue #34).\n//\n// Every render-side diagnostic — rejected colour/filter/path/typography\n// value, unknown prop, unrendered spec'd field — flows through\n// `emitDiagnostic`. The diagnostic is an EVENT, not a console.log :\n// hosts subscribe via `MountOptions.onDiagnostic` (wired by `mount()`)\n// and receive a structured `{ nodeId, field, reason }`. When no handler\n// is registered, the runtime falls back to a DEV-only `console.warn`\n// so authors still see drops during development — and `broadcast`\n// builds stay silent on the console, per the CLAUDE.md \"no logs in\n// broadcast\" rule.\n//\n// ── Hygiene contract (Bastion R9, ADR 001 §5.1) ─────────────────────\n// A diagnostic NEVER carries the value of a leaf or a prop — only the\n// node id, the field name and a STATIC reason string. Leaf values can\n// hold sensitive on-air content ; they must not transit any diagnostic\n// channel. Callers pass field names and literal reasons exclusively.\n// The R9 sentinel test (r9-sentinel.test.tsx) enforces this end to end,\n// and statically checks that `console.warn` only exists in this module.\n// ─────────────────────────────────────────────────────────────────────\n\n/** Placeholder id for nodes that don't declare an `id`. */\nexport const ANON_NODE_ID = \"<anon>\";\n\nexport interface RenderDiagnostic {\n /** `RenderNode.id` of the node the field belongs to (RC#7), or\n * `ANON_NODE_ID` when the node has none. */\n nodeId: string;\n /** Name of the field/prop concerned (e.g. `text.colour`,\n * `shape.paths.data`, `bindAnimate.opacity`). Never its value (R9). */\n field: string;\n /** Static reason — why the field was rejected or not rendered. */\n reason: string;\n}\n\nexport type DiagnosticHandler = (diagnostic: RenderDiagnostic) => void;\n\nconst handlers = new Set<DiagnosticHandler>();\n\n/**\n * Register a diagnostics handler (one per `mount()`, plus tests).\n * Returns the unregister function. Multiple concurrent mounts each\n * receive every diagnostic — node ids are bundle-scoped, so a host\n * running several mounts should disambiguate on its side.\n */\nexport function addDiagnosticsHandler(handler: DiagnosticHandler): () => void {\n handlers.add(handler);\n return () => {\n handlers.delete(handler);\n };\n}\n\n/**\n * Emit one anti-drop diagnostic. `field` and `reason` MUST be static\n * strings / field names — never interpolate a prop or leaf value (R9).\n */\nexport function emitDiagnostic(nodeId: string | undefined, field: string, reason: string): void {\n const diagnostic: RenderDiagnostic = { nodeId: nodeId ?? ANON_NODE_ID, field, reason };\n if (handlers.size > 0) {\n for (const handler of handlers) {\n try {\n handler(diagnostic);\n } catch {\n // A host handler that throws must never break the render path.\n }\n }\n return;\n }\n // DEV-only console fallback — broadcast builds log nothing.\n if (import.meta.env.DEV) {\n console.warn(\n `[lumencast] node \"${diagnostic.nodeId}\": field \"${field}\" ${reason} (value withheld per R9)`,\n );\n }\n}\n","// Runtime half of the R8 filter gate (ADR 001 §5.1 R8, issue #42).\n//\n// The compiler clamps `filter` values at lowering (`lowerFilter`,\n// packages/compiler/src/compile.ts) — but a filter value pushed by a\n// LIVE LSDP delta reaches the runtime through `resolveProps` /\n// `animateBindings` without ever passing through the compiler. R8\n// requires the clamp at compile AND at runtime : an unbounded filter is\n// a compositing DoS in CEF. Every filter value that can reach an inline\n// style at render time MUST pass through this module.\n//\n// NOTE on duplication : these caps intentionally mirror the compiler's\n// `MAX_FILTER_BLUR_PX` / `MAX_FILTER_BRIGHTNESS` constants. Unifying\n// them behind a single shared module is tracked by issue #41 (same\n// model as the shared colour module) — do NOT change one side without\n// the other until #41 lands.\n//\n// ── Linear-time justification (RC#12) ────────────────────────────────\n// The string form is validated by a single ANCHORED regex made of\n// literals and bounded quantifiers ({1,7} / {1,4} digit runs, one\n// optional space run) — exactly one possible parse per input, no\n// backtracking blow-up. Inputs longer than MAX_FILTER_STRING_LEN are\n// rejected before the regex runs.\n// ─────────────────────────────────────────────────────────────────────\n\nimport { emitDiagnostic } from \"./diagnostics\";\n\n/** Max CSS `blur()` radius accepted at runtime, in px (mirror of the\n * compiler cap — see issue #41). */\nexport const MAX_FILTER_BLUR_PX = 100;\n/** Max CSS `brightness()` factor accepted at runtime (mirror of the\n * compiler cap — see issue #41 ; spec §6.1 blesses clamping to 4). */\nexport const MAX_FILTER_BRIGHTNESS = 4;\n\nconst MAX_FILTER_STRING_LEN = 64;\n\nconst CAPS: Record<FilterChannel, number> = {\n blur: MAX_FILTER_BLUR_PX,\n brightness: MAX_FILTER_BRIGHTNESS,\n};\n\nexport type FilterChannel = \"blur\" | \"brightness\";\n\n/**\n * Gate one live numeric filter channel (R8 runtime half).\n *\n * Returns the clamped value, or `null` when the value is rejected\n * (non-number, non-finite, negative — including `-0`, which would\n * stringify to an accepted `0`). A `null` MUST be handled as \"keep the\n * last known-good value / identity\" — never apply the raw input.\n */\nexport function clampFilterChannel(channel: FilterChannel, value: unknown): number | null {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return null;\n if (value < 0 || Object.is(value, -0)) return null;\n const cap = CAPS[channel];\n return value > cap ? cap : value;\n}\n\n// The ONLY string form the compiler ever emits (`lowerFilter`) :\n// `blur(<n>px) brightness(<n>)`. Anything else — extra functions,\n// `url(`, negative signs, exponents — is rejected by construction\n// (the grammar has no `-`, no `e`, no second parenthesis pair).\nconst FILTER_STRING_RE =\n /^blur\\((\\d{1,7}(?:\\.\\d{1,4})?)px\\) brightness\\((\\d{1,7}(?:\\.\\d{1,4})?)\\)$/;\n\n/** Identity filter — matches the compiler's neutral emission and\n * `INITIAL_IDENTITY.filter` in transitions.ts. */\nexport const FILTER_IDENTITY = \"blur(0px) brightness(1)\";\n\n/**\n * Gate a CSS filter STRING reaching framer-motion at runtime\n * (`animate_initial.filter`, keyframe `steps[].filter`). Hand-crafted\n * bundles bypass the compiler clamps, so the runtime re-validates and\n * re-clamps (R8). Returns the safe, clamped canonical string or `null`\n * on rejection — never the raw input.\n */\nexport function sanitizeCssFilterString(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n if (value.length === 0 || value.length > MAX_FILTER_STRING_LEN) return null;\n const m = FILTER_STRING_RE.exec(value);\n if (!m) return null;\n const blur = clampFilterChannel(\"blur\", Number(m[1]));\n const brightness = clampFilterChannel(\"brightness\", Number(m[2]));\n if (blur === null || brightness === null) return null;\n return `blur(${blur}px) brightness(${brightness})`;\n}\n\n/**\n * Diagnostic for a rejected filter value. Bastion R9 (ADR 001 §5.1) :\n * the rejected VALUE is never logged nor forwarded — only `node.id`\n * (RC#7, issue #34), the field name and a static reason. Routed through\n * the structured diagnostics channel (events, no logs in `broadcast`).\n */\nexport function warnRejectedFilter(field: string, nodeId?: string): void {\n emitDiagnostic(\n nodeId,\n field,\n \"rejected unsafe filter value : outside the R8 caps or not a finite number >= 0\",\n );\n}\n","// Local Transition type + Framer Motion translation.\n//\n// LSML 1.0 §6 declares `animate` directives at the primitive level (transition,\n// transform, opacity, filter). LSDP/1.1 §3.2.2 added per-leaf transition\n// directives on delta patches — incoming deltas can carry a transition hint\n// that overrides the bundle-level default for the next animation cycle.\n// `parseWireTransition` ingests the wire shape ; `Store.lastTransition(path)`\n// surfaces the most-recent directive to the renderer.\n//\n// We deliberately animate only GPU-friendly properties (transform, opacity,\n// filter). Primitives enforce this at the DOM level by exposing those props as\n// motion-bindable values rather than raw CSS.\n\nimport { sanitizeCssFilterString, warnRejectedFilter } from \"../render/filter-clamp\";\n\nexport type TransitionKind = \"none\" | \"tween\" | \"spring\" | \"crossfade\";\n\nexport interface TweenTransition {\n kind: \"tween\";\n duration_ms: number;\n ease?: \"linear\" | \"cubic-in\" | \"cubic-out\" | \"cubic-in-out\";\n}\n\nexport interface SpringTransition {\n kind: \"spring\";\n stiffness?: number;\n damping?: number;\n /** LSML 1.1 §6.2 — spring mass (kg). Default 1 (framer default). */\n mass?: number;\n}\n\nexport interface CrossfadeTransition {\n kind: \"crossfade\";\n duration_ms?: number;\n}\n\nexport interface NoTransition {\n kind: \"none\";\n}\n\nexport type Transition = NoTransition | TweenTransition | SpringTransition | CrossfadeTransition;\n\nexport type FramerEasing = \"linear\" | \"easeIn\" | \"easeOut\" | \"easeInOut\";\n\nexport interface FramerTransition {\n duration?: number;\n ease?: FramerEasing;\n type?: \"tween\" | \"spring\";\n stiffness?: number;\n damping?: number;\n mass?: number;\n}\n\nconst NO_ANIMATION: FramerTransition = { duration: 0 };\n\nconst EASE_MAP: Record<string, FramerEasing> = {\n linear: \"linear\",\n \"cubic-in\": \"easeIn\",\n \"cubic-out\": \"easeOut\",\n \"cubic-in-out\": \"easeInOut\",\n};\n\nexport function toFramer(t: Transition | undefined): FramerTransition {\n if (!t || t.kind === \"none\") return NO_ANIMATION;\n if (t.kind === \"tween\") {\n return {\n type: \"tween\",\n duration: (t.duration_ms ?? 0) / 1000,\n ease: t.ease ? (EASE_MAP[t.ease] ?? \"easeOut\") : \"easeOut\",\n };\n }\n if (t.kind === \"spring\") {\n return {\n type: \"spring\",\n ...(t.stiffness !== undefined ? { stiffness: t.stiffness } : {}),\n ...(t.damping !== undefined ? { damping: t.damping } : {}),\n ...(t.mass !== undefined ? { mass: t.mass } : {}),\n };\n }\n // crossfade at the per-prop level degenerates into a tween on opacity.\n return {\n type: \"tween\",\n duration: (t.duration_ms ?? 400) / 1000,\n ease: \"easeInOut\",\n };\n}\n\n// --- mount-play (LSML 1.1 `animate.from`) ---------------------------\n\n/** Identity (animation-end) value for each framer key an `animate.from`\n * may declare. A primitive that doesn't natively animate a given key\n * still converges it to this neutral value on mount so the element ends\n * up visually correct (e.g. a `from.scale: 0.85` settles at `scale: 1`). */\nconst INITIAL_IDENTITY: Record<string, number | string> = {\n opacity: 1,\n scale: 1,\n scaleX: 1,\n scaleY: 1,\n rotate: 0,\n x: 0,\n y: 0,\n // LSML §6.1 filter identity — both functions are always present so\n // framer interpolates between structurally-identical filter lists\n // (the compiler emits the same two-function form, clamped per R8).\n filter: \"blur(0px) brightness(1)\",\n};\n\nexport interface MountPlay {\n initial: Record<string, number | string>;\n animate: Record<string, number | string>;\n}\n\n/**\n * Default mount-play timing — applies when a node carries an\n * `animate_initial` (LSML 1.1 `animate.from`) but no per-prop\n * `transitions` entry resolves for any animated key. The compiler\n * documents that `from` without an explicit `transition` mount-plays\n * \"with the runtime's default timing\" ; before this constant existed the\n * fallback was `toFramer(undefined)` → `{ duration: 0 }`, which snapped\n * the element straight to its settled state (the mount-play never\n * visibly played). 400 ms ease-out matches the runtime's other implicit\n * timings (crossfade fallback, scene-track fade).\n */\nexport const DEFAULT_MOUNT_PLAY_TRANSITION: Transition = {\n kind: \"tween\",\n duration_ms: 400,\n ease: \"cubic-out\",\n};\n\n/**\n * Resolve the transition a primitive should hand framer-motion.\n *\n * `keys` are the primitive's native animated prop keys, scanned in\n * order (e.g. `[\"opacity\", \"src\"]` for Image). When the node also\n * carries an `animate_initial`, the lookup widens to the keys the\n * mount-play actually moves (`from.scale` may have lowered a `scale`\n * transition that an opacity-only primitive would otherwise never look\n * up), and — critically — falls back to\n * `DEFAULT_MOUNT_PLAY_TRANSITION` instead of \"no animation\" : a\n * mount-play must tween, never complete in zero frames.\n *\n * Without `animate_initial` the prior behaviour is preserved exactly :\n * first declared transition among `keys`, else `undefined` (deltas\n * snap unless a transition is declared).\n */\nexport function resolveTransition(\n transitionFor: (key: string) => Transition | undefined,\n keys: string[],\n animateInitial?: Record<string, number | string>,\n): Transition | undefined {\n for (const key of keys) {\n const t = transitionFor(key);\n if (t !== undefined) return t;\n }\n if (animateInitial && Object.keys(animateInitial).length > 0) {\n for (const key of Object.keys(animateInitial)) {\n const t = transitionFor(key);\n if (t !== undefined) return t;\n }\n return DEFAULT_MOUNT_PLAY_TRANSITION;\n }\n return undefined;\n}\n\n/**\n * Build framer-motion `initial` / `animate` props for a primitive that\n * may carry an LSML 1.1 `animate.from` initial state.\n *\n * `base` is the primitive's own animated target (e.g. `{ opacity }` for\n * Image/Text/Shape, or `{ opacity, x, y, scale, rotate }` for Frame).\n * `initial` is the lowered `animate.from` map (or `undefined`).\n *\n * When `initial` is absent, this returns `{ initial: base, animate: base }`\n * — framer mounts at the target and never moves, exactly the prior\n * no-mount-play behaviour (backward compatible). When `initial` is\n * present, the element mounts at `initial` and animates to `base`,\n * augmented with identity convergence for any `from` key the primitive\n * doesn't already drive — so the mount-play plays out and settles\n * correctly even on opacity-only primitives.\n */\nexport function mountPlay(\n base: Record<string, number | string>,\n initial: Record<string, number | string> | undefined,\n nodeId?: string,\n): MountPlay {\n if (!initial || Object.keys(initial).length === 0) {\n // No `from` → mount directly at target. Pinning `initial` to the\n // target (rather than letting framer infer from current style)\n // preserves the existing \"no jump, no mount-play\" behaviour.\n return { initial: base, animate: base };\n }\n // R8 runtime half (ADR 001 §5.1, issue #42) — `animate_initial` may\n // come from a hand-crafted bundle that never went through the\n // compiler clamps. Re-gate the filter string ; rejected → drop the\n // key (the element mounts at the identity filter instead).\n let from = initial;\n if (initial[\"filter\"] !== undefined) {\n const safe = sanitizeCssFilterString(initial[\"filter\"]);\n from = { ...initial };\n if (safe === null) {\n warnRejectedFilter(\"animate_initial.filter\", nodeId);\n delete from[\"filter\"];\n } else {\n from[\"filter\"] = safe;\n }\n }\n const animate: Record<string, number | string> = { ...base };\n for (const key of Object.keys(from)) {\n if (!(key in animate)) {\n animate[key] = INITIAL_IDENTITY[key] ?? 0;\n }\n }\n return { initial: from, animate };\n}\n\n/**\n * Parse a wire-format `TransitionSpec` (LSDP/1.1 §3.2.2) into the\n * runtime's local Transition type. Returns `undefined` for malformed\n * input so the caller falls back to whatever bundle-level default\n * applies. The wire shape uses kebab-case `easing` values\n * (`linear`, `ease-in`, `ease-out`, `ease-in-out`) which we map to\n * the runtime's `cubic-*` vocabulary.\n */\nexport function parseWireTransition(value: unknown): Transition | undefined {\n if (typeof value !== \"object\" || value === null) return undefined;\n const v = value as Record<string, unknown>;\n const kind = v.kind;\n if (kind === \"snap\") {\n return { kind: \"none\" };\n }\n if (kind === \"tween\") {\n const duration_ms = typeof v.duration_ms === \"number\" ? v.duration_ms : 0;\n const easing = WIRE_EASING_MAP[v.easing as string] ?? \"cubic-out\";\n return { kind: \"tween\", duration_ms, ease: easing };\n }\n if (kind === \"spring\") {\n const out: SpringTransition = { kind: \"spring\" };\n if (typeof v.stiffness === \"number\") out.stiffness = v.stiffness;\n if (typeof v.damping === \"number\") out.damping = v.damping;\n // LSML §6.2 — mass rides the same wire spring shape.\n if (typeof v.mass === \"number\") out.mass = v.mass;\n return out;\n }\n return undefined;\n}\n\nconst WIRE_EASING_MAP: Record<string, \"linear\" | \"cubic-in\" | \"cubic-out\" | \"cubic-in-out\"> = {\n linear: \"linear\",\n \"ease-in\": \"cubic-in\",\n \"ease-out\": \"cubic-out\",\n \"ease-in-out\": \"cubic-in-out\",\n};\n","import { batch } from \"@preact/signals-react\";\nimport type { DeltaFrame } from \"@lumencast/protocol\";\nimport type { Store } from \"./store.js\";\nimport { parseWireTransition } from \"../animate/transitions\";\n\n/** Apply an LSDP/1 delta. All patches in the frame land in a single signals\n * batch — components reading multiple paths see them flip in one render pass.\n *\n * LSDP/1.1 §3.2.2 — a patch may carry a `transition` directive overriding\n * the bundle-level default for the next animation cycle on that leaf. We\n * thread it through the store so the renderer reads the correct directive\n * alongside the new value. */\nexport function applyDelta(store: Store, frame: DeltaFrame): void {\n batch(() => {\n for (const patch of frame.patches) {\n const transition = parseWireTransition(patch.transition);\n if (transition !== undefined) {\n store.setWithTransition(patch.path, patch.value, transition);\n } else {\n store.set(patch.path, patch.value);\n }\n }\n });\n}\n","import type { SnapshotFrame } from \"@lumencast/protocol\";\nimport type { Store } from \"./store.js\";\n\n/** Apply an LSDP/1 snapshot to the store. Replaces the entire state — paths\n * not present in the snapshot are reset to `undefined`. */\nexport function applySnapshot(store: Store, frame: SnapshotFrame): void {\n store.reset(frame.state);\n}\n","// Reserved `__cam.*` LSDP leaves — surfaced to the host, never rendered.\n//\n// ADR Blue 009 §3.2–3.3 (axe 1, antenne). The meet-cam antenne path needs two\n// pieces of RUNTIME, stream-level ZabCam state that travel on the LSDP wire as\n// RESERVED leaves (they bind to no node and are never painted) :\n//\n// - `__cam.slots.<slotRef>` = \"<peer_label>\" (Orion #267 deltas, §3.3)\n// which peer currently fills an authored `x-zab.meet-peer` slot.\n// - `__cam.viewer` = { rooms: [{ signalingUrl, roomId, token }] } (Orion #268)\n// the receive-only viewer credentials for the active stream.\n//\n// The runtime does NOT join rooms, hold creds, or re-key slots itself — that is\n// the host's WebRTC viewer (Solar `peer-viewer/*`). The runtime's only job is to\n// SURFACE these reserved leaves to the host through `MountOptions.onReservedLeaves`\n// so Solar can feed `__cam.viewer` into its viewer injection and drive its\n// slot-binding registry's `assign(slotRef, peer_label | null)` from `__cam.slots.*`.\n// Receive-only : the token flows host→viewer→join ; the runtime never reads it.\n\n/** Every reserved cam leaf lives under this prefix. */\nexport const CAM_RESERVED_PREFIX = \"__cam.\";\n\n/** One scalar leaf per bound slot : `__cam.slots.<slotRef>` = \"<peer_label>\"\n * (ADR Blue 009 §3.3). Mirrors Solar's `CAM_SLOTS_PREFIX` — keep them in sync. */\nexport const CAM_SLOTS_PREFIX = \"__cam.slots.\";\n\n/** The single viewer-credentials leaf (ADR Blue 009 §3.2, Orion #268). */\nexport const CAM_VIEWER_LEAF = \"__cam.viewer\";\n\n/** The reserved cam state surfaced to the host in one shot on every change. */\nexport interface ReservedCamLeaves {\n /** `__cam.viewer` — receive-only viewer creds for the active stream. Opaque to\n * the runtime (shape `{ rooms: [{ signalingUrl, roomId, token }] }` validated\n * host-side) ; `undefined` when the leaf is absent. */\n viewer?: unknown;\n /** `slotRef → peer_label` snapshot from the `__cam.slots.*` subtree. A slot\n * ABSENT from this map is UNBOUND — the host releases it (`assign(slotRef,\n * null)`) so the `x-zab.meet-peer` node falls back to its placeholder. Only\n * non-empty string values are kept. */\n slots: Record<string, string>;\n}\n\n/** A reserved cam leaf the runtime forwards rather than renders. */\nexport function isReservedCamPath(path: string): boolean {\n return path === CAM_VIEWER_LEAF || path.startsWith(CAM_SLOTS_PREFIX);\n}\n\n/** Project a raw reserved-leaf state into the host-facing shape. Defensive : a\n * malformed slot value (non-string / empty) or empty slotRef is dropped. */\nfunction project(raw: Map<string, unknown>): ReservedCamLeaves {\n const slots: Record<string, string> = {};\n let viewer: unknown;\n for (const [path, value] of raw) {\n if (path === CAM_VIEWER_LEAF) {\n if (value !== undefined && value !== null) viewer = value;\n } else if (path.startsWith(CAM_SLOTS_PREFIX)) {\n const slotRef = path.slice(CAM_SLOTS_PREFIX.length);\n if (slotRef !== \"\" && typeof value === \"string\" && value !== \"\") slots[slotRef] = value;\n }\n }\n return viewer !== undefined ? { viewer, slots } : { slots };\n}\n\n/** A stable identity key for change detection — two `ReservedCamLeaves` with the\n * same content always produce the same key regardless of insertion order. */\nfunction identity(leaves: ReservedCamLeaves): string {\n const slots = Object.keys(leaves.slots)\n .sort()\n .map((k) => `${k}=${leaves.slots[k]}`)\n .join(\"&\");\n const viewer = leaves.viewer === undefined ? \"\" : JSON.stringify(leaves.viewer);\n return `${slots}|${viewer}`;\n}\n\nexport interface ReservedLeafObserver {\n /** Reseed from a full snapshot's state (reserved leaves not present are\n * dropped). Emits when the projected state changed. */\n onSnapshot(state: Record<string, unknown>): void;\n /** Apply a delta's patches ; emits only when a reserved leaf actually moved. */\n onDelta(patches: ReadonlyArray<{ path: string; value: unknown }>): void;\n}\n\n/** Track the reserved `__cam.*` leaves across snapshots + deltas and `emit` the\n * host-facing projection whenever it changes (de-duplicated by content, so an\n * unrelated scene's deltas never call back). Created only when the host supplies\n * `onReservedLeaves` — zero cost otherwise. */\nexport function createReservedLeafObserver(\n emit: (leaves: ReservedCamLeaves) => void,\n): ReservedLeafObserver {\n const raw = new Map<string, unknown>();\n // Seed with the empty projection's identity so a plain scene (no cam leaves)\n // never fires a spurious empty emit ; a later transition to/from cam state does.\n let last = identity({ slots: {} });\n\n const flush = (): void => {\n const leaves = project(raw);\n const key = identity(leaves);\n if (key === last) return;\n last = key;\n emit(leaves);\n };\n\n return {\n onSnapshot(state) {\n raw.clear();\n for (const [path, value] of Object.entries(state)) {\n if (isReservedCamPath(path)) raw.set(path, value);\n }\n flush();\n },\n onDelta(patches) {\n let touched = false;\n for (const patch of patches) {\n if (isReservedCamPath(patch.path)) {\n raw.set(patch.path, patch.value);\n touched = true;\n }\n }\n if (touched) flush();\n },\n };\n}\n","// State store — one signal per leaf path.\n//\n// Integration point between the WS layer (snapshot + delta) and the render\n// layer. Each path Lumencast has ever seen owns a `Signal<unknown>`;\n// readers subscribe via @preact/signals-react `useSignals()` and re-render\n// only when their path's value changes.\n//\n// LSDP/1.1 §3.2.2 — incoming deltas may carry a per-leaf `transition`\n// directive. The store keeps the most-recent directive per path so the\n// renderer can pick it up on the next animation cycle. Snapshots clear\n// any pending transitions for the affected paths (snapshots are not\n// animated transitions).\n\nimport { signal, type Signal, batch } from \"@preact/signals-react\";\nimport type { Transition } from \"../animate/transitions\";\n\nexport interface Store {\n /** Get-or-create the signal for a path. New paths start as `undefined`. */\n signal(path: string): Signal<unknown>;\n /** Apply a single leaf write. */\n set(path: string, value: unknown): void;\n /** Apply a single leaf write with an LSDP/1.1 §3.2.2 transition directive.\n * The directive lives in a separate signal so the renderer can subscribe\n * to it independently. Passing `undefined` clears any pending directive. */\n setWithTransition(path: string, value: unknown, transition: Transition | undefined): void;\n /** Read the most-recent transition directive for a path (or undefined\n * when no directive has been applied since the last snapshot). The\n * returned signal is reactive — components reading via `useSignals()`\n * re-render when the directive changes. */\n transitionSignal(path: string): Signal<Transition | undefined>;\n /**\n * Replace the whole state — used by `apply-snapshot`. Existing signals are\n * reused (subscribers stay attached); paths missing from the snapshot reset\n * to `undefined`. Pending per-path transitions are cleared (a snapshot is\n * a state restore, not an animated change).\n */\n reset(state: Record<string, unknown>): void;\n /** Snapshot of every known path → current value. For debug / state inspector. */\n toRecord(): Record<string, unknown>;\n}\n\nclass StoreImpl implements Store {\n private readonly signals = new Map<string, Signal<unknown>>();\n private readonly transitions = new Map<string, Signal<Transition | undefined>>();\n\n signal(path: string): Signal<unknown> {\n let s = this.signals.get(path);\n if (!s) {\n s = signal<unknown>(undefined);\n this.signals.set(path, s);\n }\n return s;\n }\n\n transitionSignal(path: string): Signal<Transition | undefined> {\n let s = this.transitions.get(path);\n if (!s) {\n s = signal<Transition | undefined>(undefined);\n this.transitions.set(path, s);\n }\n return s;\n }\n\n set(path: string, value: unknown): void {\n const s = this.signal(path);\n if (!shallowEqual(s.peek(), value)) {\n s.value = value;\n }\n }\n\n setWithTransition(path: string, value: unknown, transition: Transition | undefined): void {\n batch(() => {\n const ts = this.transitionSignal(path);\n // Update transition before value so the render that observes the\n // new value sees the correct transition.\n if (ts.peek() !== transition) ts.value = transition;\n const s = this.signal(path);\n if (!shallowEqual(s.peek(), value)) s.value = value;\n });\n }\n\n reset(state: Record<string, unknown>): void {\n batch(() => {\n const seen = new Set<string>();\n for (const [path, value] of Object.entries(state)) {\n seen.add(path);\n const s = this.signal(path);\n if (!shallowEqual(s.peek(), value)) {\n s.value = value;\n }\n // Snapshots are not animated transitions — clear any pending\n // per-path directive (LSDP/1.1 §3.2.2 — directives apply to\n // the NEXT delta only, snapshots reseed state authoritatively).\n const ts = this.transitions.get(path);\n if (ts && ts.peek() !== undefined) ts.value = undefined;\n }\n for (const path of this.signals.keys()) {\n if (!seen.has(path)) {\n const s = this.signals.get(path);\n if (s && s.peek() !== undefined) s.value = undefined;\n }\n }\n });\n }\n\n toRecord(): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [path, s] of this.signals.entries()) {\n out[path] = s.peek();\n }\n return out;\n }\n}\n\nexport function createStore(): Store {\n return new StoreImpl();\n}\n\nfunction shallowEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== typeof b) return false;\n if (typeof a !== \"object\") return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n }\n const ao = a as Record<string, unknown>;\n const bo = b as Record<string, unknown>;\n const ak = Object.keys(ao);\n const bk = Object.keys(bo);\n if (ak.length !== bk.length) return false;\n for (const k of ak) {\n if (ao[k] !== bo[k]) return false;\n }\n return true;\n}\n","// Render bundle — the runtime's flat, pre-compiled representation of a scene.\n//\n// The bundle is content-addressed by `scene_version` (sha256 of the\n// canonical JSON form). Lumencast fetches it once per `scene_version` and\n// caches forever; the server serves it with long-TTL immutable cache headers.\n//\n// Note on shape: this `RenderBundle` is the flat, runtime-internal form. The\n// canonical *authoring* format (LSML 1.0, see lumencast-protocol/spec/LSML-1.md)\n// uses inline `bind: { value: \"path\" }` per primitive instead of a `bindings`\n// map. A compiler step (forthcoming `@lumencast/compiler`) will translate\n// LSML 1.0 → RenderBundle. For now, callers who want to feed an LSML 1.0\n// bundle pre-compile or use a hand-rolled adapter.\n\nimport type { Transition } from \"../animate/transitions.js\";\nimport type { Keyframes } from \"../animate/keyframes.js\";\n\n// --- bundle shape ----------------------------------------------------\n\nexport type RenderKind =\n | \"stack\"\n | \"grid\"\n | \"frame\"\n | \"text\"\n | \"image\"\n | \"shape\"\n | \"media\"\n | \"repeat\"\n | \"instance\"\n // ADR 006 §3.3/§3.5 — the unified source kind. Every source crossing the\n // Prism export arrives as a `meet.peer` node ; the runtime resolves its\n // `peer_label → MediaStream` (WebRTC viewer) and renders it in `srcObject`.\n | \"meet.peer\"\n // Zab vendor primitive (RFC-0001, §17.1) — a transparent capture\n // placeholder. Recognised by the Zab-plugin runtime ; reserves a box and\n // renders nothing.\n | \"x-zab.capture\"\n // Zab vendor primitive (ADR Blue 009 §3.1, Amendment 2) — a transparent\n // meet-peer SLOT placeholder. Declares only a logical `x-zab.slotRef` (which\n // slot receives a meet peer) + geometry ; carries NO cam/peer identity. The\n // runtime resolves `slotRef → peer_label` from stream-level ZabCam state and\n // renders the bound peer ; an unbound slot renders a transparent box.\n | \"x-zab.meet-peer\";\n\nexport interface RenderNode {\n kind: RenderKind;\n /** Stable identifier for keyed reconciliation. */\n id?: string;\n /** Static props (frozen at build/compile time). */\n props?: Record<string, unknown>;\n /** Prop name → state path. The render layer subscribes the path's signal\n * and applies the value to the named prop on each change. */\n bindings?: Record<string, string>;\n /** Default transition per bound prop. Aligns with LSML 1.0 §6 `animate`\n * directives. The runtime applies these as CSS transitions / Framer Motion\n * configs at render time. */\n transitions?: Record<string, Transition>;\n /** LSML 1.1 §6 `animate.from` — mount-time initial state, lowered to a\n * flat framer-motion `initial` map (keys: `opacity`, `scale`, `rotate`,\n * `x`, `y`, `filter`). When present, the rendering primitive passes this\n * as framer-motion `initial={...}` so the element mounts in this state\n * and animates to its declared target on mount (mount-play). When absent,\n * the primitive applies no `initial` and the prior no-mount-play\n * behaviour holds (backward compatible). */\n animate_initial?: Record<string, number | string>;\n /** LSML 1.1 §6.3 — animation targets bound to leaf paths\n * (`bindAnimate`). Keys are the spec property names (`opacity`,\n * `transform.translate`, `transform.scale`, `transform.rotate`,\n * `filter.blur`, `filter.brightness`, plus the kind's colour-typed\n * property per §6.5 : `style.color` / `fill` / `background`) ; values\n * are LeafPaths. The runtime subscribes each path's leaf-grain signal\n * and retargets a Framer motion value on change — continuous\n * interpolation toward the live value, no remount. Deltas are\n * coalesced per frame (one retarget max per rAF per binding,\n * ADR 001 RC#13). */\n animateBindings?: Record<string, string>;\n /** LSML 1.1 §6.6 — multi-step keyframe sequence played on mount or\n * whenever `keyframes.key` (LeafPath) changes. Coexists with\n * `transitions` ; the runtime applies whichever was last triggered\n * (no blending — see §6.6 last paragraph). */\n keyframes?: Keyframes;\n /** LSML 1.1 §6.7 — only meaningful on `repeat`. Each iteration's\n * animations start `index * stagger_ms` after iteration 0. */\n stagger_ms?: number;\n /** Children — already-inlined primitives only. */\n children?: RenderNode[];\n}\n\nexport type OperatorInputType =\n | \"boolean\"\n | \"number\"\n | \"text\"\n | \"select\"\n | \"enum\"\n | \"path-ref\"\n | \"colour\"\n | \"duration\";\n\nexport interface OperatorInput {\n path: string;\n label: string;\n type: OperatorInputType;\n default?: unknown;\n group?: string;\n writable_by?: string[];\n [extra: string]: unknown;\n}\n\nexport interface ExternalAdapter {\n key: string;\n label: string;\n kind: string;\n target_paths: string[];\n [extra: string]: unknown;\n}\n\nexport interface Asset {\n id: string;\n url: string;\n kind: string;\n [extra: string]: unknown;\n}\n\nexport interface RenderBundle {\n scene_version: string;\n root: RenderNode;\n operator_inputs?: OperatorInput[];\n external_adapters?: ExternalAdapter[];\n /** Bundle-level asset declarations. `allowedHosts` is the host\n * allowlist (LSML 1.2 §3.2, Bastion T1) every image / image-fill `src`\n * is gated against at render BEFORE reaching the DOM. Absent / empty =\n * deny every remote host (deny-by-default). Other keys (`fonts`,\n * `preload`) are forwarded opaquely by the compiler. */\n assets?: { allowedHosts?: string[]; [key: string]: unknown };\n /** LSML 1.1 §17.3 — capability profiles required for correct rendering.\n * Each entry is an `x-<vendor>.<name>/<version>` string. The runtime\n * checks every behavioural entry against its supported list ; an\n * unrecognised behavioural profile raises BUNDLE_INCOMPATIBLE per\n * §17.3.1. Authoring profiles (`x-<vendor>.authoring/<major>`, §17.5.1)\n * are advisory : ignored at render time, never a rejection cause. */\n profiles?: string[];\n}\n\n/**\n * Profiles the JS runtime advertises support for. Bundle authors who\n * declare `profiles: [...]` get a hard `BUNDLE_INCOMPATIBLE` rejection\n * when any entry is not in this set (LSML 1.1 §17.3.1).\n *\n * 1.1 ships with no standard profiles ; future minors / vendor specs\n * register here. The `x-lumencast.color-srgb-1.0` entry is the\n * default-color-space marker ; bundles that opt into a perceptual\n * space (OKLCH) would request a different profile and currently\n * reject.\n */\nexport const SUPPORTED_PROFILES: ReadonlySet<string> = new Set<string>([\n \"x-lumencast.color-srgb-1.0\",\n // RFC-0001 / ADR 004 — this runtime ships the Zab capture plugin, so a\n // bundle declaring `x-zab.capture/1` in `profiles[]` is compatible (it is\n // NOT rejected as BUNDLE_INCOMPATIBLE, §17.3.1).\n \"x-zab.capture/1\",\n]);\n\n// LSML 1.1 §17.5.1 + ADR 001 RC#14 — authoring-profile detection.\n//\n// An authoring profile is advisory : a runtime that does not support it\n// MUST NOT reject the bundle and renders the underlying primitives as if\n// the profile were absent. Detection matches the COMPLETE identifier form\n// `x-<vendor>.authoring/<major>` :\n//\n// - `x-` prefix, then one or more lowercase name segments separated by\n// dots, where `.authoring` is the EXACT TERMINAL segment before `/` ;\n// - `<major>` is a bare integer (no `.minor`) ;\n// - never a substring test : a behavioural profile whose name merely\n// *contains* `.authoring` in a non-terminal position is NOT exempted\n// and keeps §17.3.1 hard-rejection semantics.\n//\n// Anti-ReDoS note : both regexes below are anchored and unambiguous —\n// the character classes exclude the `.` and `/` separators, so there is\n// exactly one possible parse per input (linear time, no backtracking).\nconst AUTHORING_NAME_RE = /^x-[a-z0-9-]+(?:\\.[a-z0-9-]+)*$/;\nconst AUTHORING_MAJOR_RE = /^(?:0|[1-9][0-9]*)$/;\nconst AUTHORING_SUFFIX = \".authoring\";\n\n/** True when `id` has the complete authoring-profile form\n * `x-<vendor>.authoring/<major>` (LSML 1.1 §17.5.1, ADR 001 RC#14).\n * Such profiles are advisory : ignored at render time, never rejected. */\nexport function isAuthoringProfile(id: string): boolean {\n const slash = id.indexOf(\"/\");\n if (slash < 0) return false;\n const name = id.slice(0, slash);\n const major = id.slice(slash + 1);\n if (!AUTHORING_MAJOR_RE.test(major)) return false;\n if (!name.endsWith(AUTHORING_SUFFIX)) return false;\n // `name` minus the terminal `.authoring` segment must still be a valid\n // `x-<vendor>[.<segment>...]` prefix — this is what makes `.authoring`\n // a real terminal segment rather than a substring of another one\n // (e.g. `x-evilauthoring/1` or `x-evil.authoring.fx/1` do not match).\n return AUTHORING_NAME_RE.test(name.slice(0, -AUTHORING_SUFFIX.length));\n}\n\nexport class BundleIncompatibleError extends Error {\n public readonly code = \"BUNDLE_INCOMPATIBLE\" as const;\n public readonly unsupportedProfiles: string[];\n constructor(unsupportedProfiles: string[]) {\n super(\n `BUNDLE_INCOMPATIBLE: profile(s) not supported by this runtime: ${unsupportedProfiles.join(\n \", \",\n )}`,\n );\n this.name = \"BundleIncompatibleError\";\n this.unsupportedProfiles = unsupportedProfiles;\n }\n}\n\n/** Validate a bundle's `profiles[]` against the runtime's supported\n * set. Throws `BundleIncompatibleError` listing every offending entry\n * when at least one behavioural profile is not supported.\n *\n * Authoring profiles (`x-<vendor>.authoring/<major>`, LSML 1.1 §17.5.1)\n * are advisory and skipped : their absence from the supported set is\n * never a rejection cause. Every other (behavioural) unsupported profile\n * keeps the hard §17.3.1 `BUNDLE_INCOMPATIBLE` rejection.\n *\n * Malformed-shape guard : `bundle` may come from an unchecked\n * `json as RenderBundle` cast on untrusted server JSON\n * (`FetcherImpl.get`). A non-array `profiles` or a non-string entry is\n * therefore reachable at runtime and is rejected as\n * `BundleIncompatibleError` (typed, code BUNDLE_INCOMPATIBLE) — never a\n * raw TypeError. The diagnostic never echoes the malformed value, only a\n * shape placeholder. */\nexport function validateBundleProfiles(\n bundle: { profiles?: string[] },\n supported: ReadonlySet<string> = SUPPORTED_PROFILES,\n): void {\n const profiles: unknown = bundle.profiles;\n if (!profiles) return;\n if (!Array.isArray(profiles)) {\n throw new BundleIncompatibleError([\"<malformed: profiles is not an array>\"]);\n }\n if (profiles.length === 0) return;\n const missing = profiles\n .filter((p) => typeof p !== \"string\" || (!isAuthoringProfile(p) && !supported.has(p)))\n .map((p) => (typeof p === \"string\" ? p : \"<malformed: non-string profile entry>\"));\n if (missing.length > 0) {\n throw new BundleIncompatibleError(missing);\n }\n}\n\n// --- fetch + cache ---------------------------------------------------\n\nexport interface BundleFetcher {\n /** Fetch the bundle for a scene version. Cached forever by hash. */\n get(sceneId: string, sceneVersion: string): Promise<RenderBundle>;\n /** Inject a bundle directly — used by tests and for the \"scene already in\n * flight\" handoff path. */\n preload(bundle: RenderBundle): void;\n}\n\n/** Resolves the absolute URL of a scene's render bundle. Supplied by the\n * host (`MountOptions.resolveBundleUrl`) when the server is not at the\n * default host-root LSDP/1 layout — e.g. reached through a gateway prefix\n * (`https://gw/orion/api/v1/scenes/{id}/render-bundle?v={hash}`). */\nexport type BundleUrlResolver = (sceneId: string, sceneVersion: string) => string;\n\nexport interface BundleFetcherOptions {\n /** Base URL of the server. The fetcher constructs\n * `${baseUrl}/lsdp/v1/scenes/{id}/bundle?v={hash}`. Ignored when\n * `resolveUrl` is provided. */\n baseUrl: string;\n /** Path prefix for bundle resolution. Defaults to `/lsdp/v1/scenes`.\n * Ignored when `resolveUrl` is provided. */\n pathPrefix?: string;\n /** When set, takes full control of URL construction — the host owns the\n * whole URL (base, path prefix and `/bundle` vs `/render-bundle` suffix).\n * Lets a gateway-prefixed server be addressed without changing the\n * host-root default. */\n resolveUrl?: BundleUrlResolver;\n /** Resolve the bearer token used to authenticate each bundle GET. The\n * render-bundle endpoint is auth-gated identically to the LSDP/1 WS\n * subscription, so the fetch carries the same session token as\n * `Authorization: Bearer <token>`. Resolved per request so a token swap\n * (`setToken`) takes effect on the next fetch ; a `LumencastTokenProvider`\n * is awaited. When omitted — or when it resolves to an empty/undefined\n * value — no `Authorization` header is sent (v0.5.0 behaviour). */\n getAuthToken?: () => string | undefined | Promise<string | undefined>;\n fetchImpl?: typeof fetch;\n}\n\nclass FetcherImpl implements BundleFetcher {\n private readonly cache = new Map<string, RenderBundle>();\n private readonly inFlight = new Map<string, Promise<RenderBundle>>();\n private readonly baseUrl: string;\n private readonly pathPrefix: string;\n private readonly resolveUrl: BundleUrlResolver | undefined;\n private readonly getAuthToken: BundleFetcherOptions[\"getAuthToken\"];\n private readonly fetchImpl: typeof fetch;\n\n constructor(opts: BundleFetcherOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/$/, \"\");\n this.pathPrefix = (opts.pathPrefix ?? \"/lsdp/v1/scenes\").replace(/\\/$/, \"\");\n this.resolveUrl = opts.resolveUrl;\n this.getAuthToken = opts.getAuthToken;\n this.fetchImpl = opts.fetchImpl ?? globalThis.fetch.bind(globalThis);\n }\n\n /** Build the request init carrying the bearer token, if any. Returns\n * `undefined` when no token is available — the fetch stays header-less,\n * preserving v0.5.0 behaviour. */\n private async buildInit(): Promise<RequestInit | undefined> {\n if (!this.getAuthToken) return undefined;\n const token = await this.getAuthToken();\n if (!token) return undefined;\n return { headers: { Authorization: `Bearer ${token}` } };\n }\n\n private buildUrl(sceneId: string, sceneVersion: string): string {\n if (this.resolveUrl) {\n return this.resolveUrl(sceneId, sceneVersion);\n }\n return `${this.baseUrl}${this.pathPrefix}/${encodeURIComponent(sceneId)}/bundle?v=${encodeURIComponent(sceneVersion)}`;\n }\n\n preload(bundle: RenderBundle): void {\n // LSML 1.1 §17.3.1 — reject early if any declared profile is\n // unsupported by this runtime. Authors get an actionable error\n // instead of a silent rendering glitch.\n validateBundleProfiles(bundle);\n this.cache.set(bundle.scene_version, bundle);\n }\n\n async get(sceneId: string, sceneVersion: string): Promise<RenderBundle> {\n const cached = this.cache.get(sceneVersion);\n if (cached) return cached;\n // Bundles are content-addressed by scene_version, so a concurrent miss for\n // the same version can share a single fetch. The entry is cleared in\n // `finally` — a rejected fetch is never cached, so a later call retries.\n const existing = this.inFlight.get(sceneVersion);\n if (existing) return existing;\n const promise = this.fetchBundle(sceneId, sceneVersion).finally(() => {\n this.inFlight.delete(sceneVersion);\n });\n this.inFlight.set(sceneVersion, promise);\n return promise;\n }\n\n private async fetchBundle(sceneId: string, sceneVersion: string): Promise<RenderBundle> {\n const url = this.buildUrl(sceneId, sceneVersion);\n const init = await this.buildInit();\n const response = init ? await this.fetchImpl(url, init) : await this.fetchImpl(url);\n if (!response.ok) {\n throw new Error(`bundle fetch failed: ${response.status} ${response.statusText}`);\n }\n const json = (await response.json()) as RenderBundle;\n if (json.scene_version !== sceneVersion) {\n throw new Error(\n `bundle scene_version mismatch: expected ${sceneVersion}, got ${json.scene_version}`,\n );\n }\n validateBundleProfiles(json);\n this.cache.set(sceneVersion, json);\n return json;\n }\n}\n\nexport function createBundleFetcher(opts: BundleFetcherOptions): BundleFetcher {\n return new FetcherImpl(opts);\n}\n","// Exponential backoff schedule for the WS reconnect loop.\n//\n// Aggressive at first (200 ms, 400 ms, 800 ms…) then capped at 5 s so a\n// sustained outage doesn't hammer the gateway. Jittered to avoid thundering\n// herds when several Lumencast instances reconnect together (e.g. a CEF\n// host + a webview waking from suspend at the same time).\n//\n// The reference schedule in LSDP/1 §7 is \"0 ms, 500 ms, 1 s, 2 s, 4 s, 8 s,\n// 15 s, 30 s, 60 s cap\". This implementation defaults are tighter; both are\n// MAY-bounded by the spec.\n\nexport interface ReconnectScheduleOptions {\n /** First delay in milliseconds. */\n initial?: number;\n /** Maximum delay in milliseconds. */\n max?: number;\n /** Multiplicative factor between attempts (>= 1). */\n factor?: number;\n /** Jitter as a fraction of the delay (0 disables, 0.2 = ±20 %). */\n jitter?: number;\n /** Random source — only injected for tests. */\n random?: () => number;\n}\n\nconst DEFAULTS: Required<Omit<ReconnectScheduleOptions, \"random\">> = {\n initial: 200,\n max: 5_000,\n factor: 2,\n jitter: 0.2,\n};\n\nexport interface ReconnectSchedule {\n /** Returns the delay to wait before the n-th attempt (1-indexed). */\n delayFor(attempt: number): number;\n /** Reset to attempt 1 (called on a successful connection). */\n reset(): void;\n /** Current attempt counter. */\n readonly attempt: number;\n}\n\nclass ScheduleImpl implements ReconnectSchedule {\n private _attempt = 0;\n constructor(\n private readonly opts: Required<Omit<ReconnectScheduleOptions, \"random\">>,\n private readonly random: () => number,\n ) {}\n\n get attempt(): number {\n return this._attempt;\n }\n\n delayFor(attempt: number): number {\n if (!Number.isInteger(attempt) || attempt < 1) {\n throw new RangeError(`attempt must be a positive integer, got ${attempt}`);\n }\n this._attempt = attempt;\n const base = Math.min(\n this.opts.initial * Math.pow(this.opts.factor, attempt - 1),\n this.opts.max,\n );\n if (this.opts.jitter <= 0) return base;\n const offset = (this.random() * 2 - 1) * this.opts.jitter * base;\n return Math.max(0, base + offset);\n }\n\n reset(): void {\n this._attempt = 0;\n }\n}\n\nexport function createReconnectSchedule(opts: ReconnectScheduleOptions = {}): ReconnectSchedule {\n const merged = {\n initial: opts.initial ?? DEFAULTS.initial,\n max: opts.max ?? DEFAULTS.max,\n factor: opts.factor ?? DEFAULTS.factor,\n jitter: opts.jitter ?? DEFAULTS.jitter,\n };\n if (merged.initial <= 0) throw new RangeError(\"initial must be > 0\");\n if (merged.max < merged.initial) throw new RangeError(\"max must be >= initial\");\n if (merged.factor < 1) throw new RangeError(\"factor must be >= 1\");\n if (merged.jitter < 0 || merged.jitter > 1) throw new RangeError(\"jitter must be within [0, 1]\");\n return new ScheduleImpl(merged, opts.random ?? Math.random);\n}\n","// LSDP/1 WebSocket client.\n//\n// Lifecycle (LSDP/1 §6):\n// 1. open() — opens the WS with subprotocol `lsdp.v1`\n// 2. on open: send `subscribe` with the resolved token (and scene + session\n// for test mode)\n// 3. server replies `snapshot` (seq=1) → emit onSnapshot\n// 4. subsequent `delta` / `scene_changed` / `error` / `pong` are dispatched\n// 5. on a sequence gap → close + reconnect (fresh snapshot)\n// 6. on close → reconnect with backoff, unless close was triggered by close()\n// 7. setToken() opens a parallel WS with the new token; once its snapshot\n// lands, atomically swap and close the old socket — no rendering gap\n\nimport {\n decodeServerFrame,\n encodeFrame,\n LumencastError,\n SequenceTracker,\n WS_SUBPROTOCOL_V1_1,\n WS_SUBPROTOCOLS,\n input as inputFrame,\n subscribe as subscribeFrame,\n type DeltaFrame,\n type ErrorCode,\n type ErrorFrame,\n type Patch,\n type SceneChangedFrame,\n type SceneRosterFrame,\n type SnapshotFrame,\n} from \"@lumencast/protocol\";\nimport type { LumencastToken } from \"../types.js\";\nimport {\n createReconnectSchedule,\n type ReconnectSchedule,\n type ReconnectScheduleOptions,\n} from \"./reconnect.js\";\n\nexport type ConnectionStatus = \"disconnected\" | \"connecting\" | \"live\";\n\nexport interface WsClientOptions {\n url: string;\n token: LumencastToken;\n /** Optional scene identifier (test mode). */\n scene?: string;\n /** Optional session identifier (test mode). */\n session?: string;\n /** Override the WebSocket constructor (for tests / non-browser hosts). */\n webSocketImpl?: typeof WebSocket;\n /** Reconnect tuning. */\n reconnect?: ReconnectScheduleOptions;\n /** Inject scheduler for tests. */\n scheduler?: {\n setTimeout: typeof globalThis.setTimeout;\n clearTimeout: typeof globalThis.clearTimeout;\n };\n\n onStatus?: (status: ConnectionStatus) => void;\n onSnapshot?: (frame: SnapshotFrame) => void;\n onDelta?: (frame: DeltaFrame) => void;\n onSceneChanged?: (frame: SceneChangedFrame) => void;\n /** Out-of-band roster advertisement (LSDP/1.1 `scene_roster`, additive).\n * Carries no sequence — dispatched without touching the sequence tracker. */\n onSceneRoster?: (frame: SceneRosterFrame) => void;\n onServerError?: (frame: ErrorFrame) => void;\n /** Wire-level / codec / unrecoverable errors. */\n onTransportError?: (err: TransportError) => void;\n}\n\nexport class TransportError extends Error {\n public readonly recoverable: boolean;\n public readonly code: ErrorCode;\n public override readonly cause?: unknown;\n constructor(\n message: string,\n recoverable: boolean,\n code: ErrorCode = \"INTERNAL\",\n cause?: unknown,\n ) {\n super(message);\n this.name = \"TransportError\";\n this.recoverable = recoverable;\n this.code = code;\n this.cause = cause;\n }\n}\n\ntype Timer = ReturnType<typeof setTimeout>;\n\ninterface InternalScheduler {\n setTimeout: typeof globalThis.setTimeout;\n clearTimeout: typeof globalThis.clearTimeout;\n}\n\nexport class WsClient {\n private status: ConnectionStatus = \"disconnected\";\n private socket: WebSocket | null = null;\n private token: LumencastToken;\n private readonly url: string;\n private readonly WebSocketCtor: typeof WebSocket;\n private readonly schedule: ReconnectSchedule;\n private readonly seq = new SequenceTracker();\n private readonly opts: WsClientOptions;\n private readonly scheduler: InternalScheduler;\n\n private reconnectTimer: Timer | null = null;\n private active = true;\n\n constructor(opts: WsClientOptions) {\n this.opts = opts;\n this.url = opts.url;\n this.token = opts.token;\n const ctor = opts.webSocketImpl ?? globalThis.WebSocket;\n if (!ctor) {\n throw new TypeError(\n \"Lumencast WsClient: no WebSocket implementation found in this environment\",\n );\n }\n this.WebSocketCtor = ctor;\n this.schedule = createReconnectSchedule(opts.reconnect);\n this.scheduler = opts.scheduler ?? {\n setTimeout: globalThis.setTimeout.bind(globalThis),\n clearTimeout: globalThis.clearTimeout.bind(globalThis),\n };\n }\n\n /** Open and start the connection lifecycle. Idempotent. */\n start(): void {\n if (!this.active) return;\n if (this.socket || this.status === \"connecting\") return;\n void this.openSocket();\n }\n\n /** Resolve the current session token (the one used for the WS\n * subscription). Mirrors `setToken` swaps. Used by the bundle fetcher to\n * authenticate the render-bundle GET with the same credential. A\n * `LumencastTokenProvider` is awaited. */\n resolveCurrentToken(): Promise<string> {\n return resolveToken(this.token);\n }\n\n /** Send `input` patches to the server. No-op if not connected. */\n sendInput(patches: Patch[]): void {\n if (!this.socket || this.socket.readyState !== this.WebSocketCtor.OPEN) return;\n if (patches.length === 0) return;\n this.socket.send(encodeFrame(inputFrame(patches)));\n }\n\n /** Replace the auth token. Closes and reopens with the new token. */\n setToken(token: LumencastToken): void {\n this.token = token;\n if (!this.active) return;\n if (this.socket) {\n this.closeSocket();\n this.scheduleReconnect(true);\n }\n }\n\n /** Tear down for good. No more reconnect attempts. */\n close(): void {\n if (!this.active) return;\n this.active = false;\n this.cancelReconnect();\n this.closeSocket();\n this.setStatus(\"disconnected\");\n }\n\n // --- internals --------------------------------------------------\n\n private async openSocket(): Promise<void> {\n if (!this.active) return;\n this.setStatus(\"connecting\");\n\n let resolvedToken: string;\n try {\n resolvedToken = await resolveToken(this.token);\n } catch (err) {\n this.opts.onTransportError?.(\n new TransportError(\n `failed to resolve token: ${(err as Error).message}`,\n true,\n \"AUTH_DENIED\",\n err,\n ),\n );\n this.scheduleReconnect();\n return;\n }\n if (!this.active) return;\n\n let socket: WebSocket;\n try {\n // Advertise both 1.1 (preferred) and 1.0 (fallback) ; the server\n // picks one. Spread to a mutable array — the WebSocket constructor\n // type expects string[].\n socket = new this.WebSocketCtor(this.url, [...WS_SUBPROTOCOLS]);\n } catch (err) {\n this.opts.onTransportError?.(\n new TransportError(\n `failed to open WebSocket: ${(err as Error).message}`,\n true,\n \"INTERNAL\",\n err,\n ),\n );\n this.scheduleReconnect();\n return;\n }\n\n this.socket = socket;\n socket.onopen = () => this.handleOpen(resolvedToken);\n socket.onmessage = (event) => this.handleMessage(event);\n socket.onerror = (event) => this.handleError(event);\n socket.onclose = (event) => this.handleClose(event);\n }\n\n private handleOpen(token: string): void {\n if (!this.socket) return;\n // LSDP/1.1 §4.1, §18 — if we have a previously-observed seq AND the\n // negotiated subprotocol is 1.1, request an incremental resume.\n // The server will EITHER ship buffered deltas (cache stays valid)\n // OR a fresh snapshot which we rebase via observeSnapshot.\n const subprotocol = this.socket.protocol;\n const canResume = subprotocol === WS_SUBPROTOCOL_V1_1 && this.seq.last > 0;\n const sinceSequence = canResume ? this.seq.last : undefined;\n if (!canResume) {\n // Fresh subscription (no resume) — reset the tracker baseline.\n this.seq.reset();\n }\n const frame = subscribeFrame({\n token,\n ...(this.opts.scene !== undefined ? { scene: this.opts.scene } : {}),\n ...(this.opts.session !== undefined ? { session: this.opts.session } : {}),\n ...(sinceSequence !== undefined ? { since_sequence: sinceSequence } : {}),\n });\n this.socket.send(encodeFrame(frame));\n }\n\n private handleMessage(event: MessageEvent): void {\n const data = typeof event.data === \"string\" ? event.data : \"\";\n if (!data) return;\n let frame;\n try {\n frame = decodeServerFrame(data);\n } catch (err) {\n const message = err instanceof LumencastError ? err.message : (err as Error).message;\n const code: ErrorCode = err instanceof LumencastError ? err.code : \"INTERNAL\";\n this.opts.onTransportError?.(new TransportError(`codec: ${message}`, true, code, err));\n this.closeSocket();\n this.scheduleReconnect();\n return;\n }\n if (frame === null) return; // unknown frame type — forward-compat ignore\n\n switch (frame.type) {\n case \"snapshot\": {\n // LSDP/1.1 §18.1.1 — snapshot rebases the tracker to its seq\n // value. This is the ONLY valid way to set or change the\n // tracker's baseline (handles fresh sub, scene_changed, and\n // back-pressure recovery uniformly).\n if (frame.seq < 1) {\n this.opts.onTransportError?.(\n new TransportError(`snapshot seq must be >= 1, got ${frame.seq}`, true, \"VERSION_GAP\"),\n );\n this.closeSocket();\n this.scheduleReconnect();\n return;\n }\n this.seq.observeSnapshot(frame.seq);\n this.schedule.reset();\n this.setStatus(\"live\");\n this.opts.onSnapshot?.(frame);\n return;\n }\n case \"delta\": {\n const obs = this.seq.observe(frame.seq);\n if (obs.kind === \"gap\") {\n this.opts.onTransportError?.(\n new TransportError(\n `sequence gap: expected ${this.seq.last + 1}, got ${frame.seq}`,\n true,\n \"VERSION_GAP\",\n ),\n );\n this.closeSocket();\n this.scheduleReconnect();\n return;\n }\n if (obs.kind === \"duplicate\") return; // silent drop per LSDP/1 §5\n this.opts.onDelta?.(frame);\n return;\n }\n case \"scene_changed\": {\n // The next snapshot will rebase the tracker via observeSnapshot.\n // Reset here so the tracker doesn't fault on the SceneChanged's\n // own seq (which advances prev's counter one final step).\n this.seq.reset();\n this.opts.onSceneChanged?.(frame);\n return;\n }\n case \"error\": {\n this.opts.onServerError?.(frame);\n if (!frame.recoverable) this.close();\n return;\n }\n case \"scene_roster\": {\n // Out-of-band roster metadata: NOT part of the snapshot/delta stream.\n // It carries no seq and MUST NOT touch the sequence tracker — forward\n // it verbatim so the host can warm its render-bundle cache.\n this.opts.onSceneRoster?.(frame);\n return;\n }\n case \"pong\":\n return;\n }\n }\n\n private handleError(_event: Event): void {\n // The browser does not give us a real reason — `close` will follow.\n }\n\n private handleClose(event: CloseEvent): void {\n this.socket = null;\n if (!this.active) {\n this.setStatus(\"disconnected\");\n return;\n }\n if (event.code === 4401 || event.code === 4403 || event.code === 1008) {\n // Auth-related close codes: not recoverable without operator intervention.\n this.opts.onTransportError?.(\n new TransportError(`server closed: ${event.code} ${event.reason}`, false, \"AUTH_DENIED\"),\n );\n this.close();\n return;\n }\n this.scheduleReconnect();\n }\n\n private scheduleReconnect(immediate = false): void {\n if (!this.active) return;\n this.cancelReconnect();\n const attempt = (this.schedule.attempt || 0) + 1;\n const delay = immediate ? 0 : this.schedule.delayFor(attempt);\n this.setStatus(\"disconnected\");\n this.reconnectTimer = this.scheduler.setTimeout(() => {\n this.reconnectTimer = null;\n void this.openSocket();\n }, delay);\n }\n\n private cancelReconnect(): void {\n if (this.reconnectTimer) {\n this.scheduler.clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n\n private closeSocket(): void {\n if (this.socket) {\n try {\n this.socket.close(1000, \"client closing\");\n } catch {\n // ignore\n }\n this.socket = null;\n }\n }\n\n private setStatus(next: ConnectionStatus): void {\n if (this.status === next) return;\n this.status = next;\n this.opts.onStatus?.(next);\n }\n}\n\nasync function resolveToken(token: LumencastToken): Promise<string> {\n if (typeof token === \"string\") return token;\n return await token.fetch();\n}\n","import type { MountOptions } from \"../types\";\n\n/** Throws on invalid mount options. Exposed separately so unit tests\n * can exercise it without mounting a real React root. */\nexport function validateOptions(options: MountOptions): void {\n if (!(options.target instanceof HTMLElement)) {\n throw new TypeError(\"mount: `target` must be an HTMLElement\");\n }\n if (typeof options.serverUrl !== \"string\" || options.serverUrl.length === 0) {\n throw new TypeError(\"mount: `serverUrl` must be a non-empty string\");\n }\n if (options.mode === \"test\") {\n if (!options.testSession) {\n throw new TypeError(\"mount: `testSession` is required when mode === 'test'\");\n }\n if (!options.scene) {\n throw new TypeError(\"mount: `scene` is required when mode === 'test'\");\n }\n }\n}\n","// Public mount() entry — the only surface a host (browser, CEF, OBS plugin,\n// iframe) interacts with. Lifecycle and contract: see RUNTIME-API.md.\n\nimport { signal } from \"@preact/signals-react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { createElement } from \"react\";\nimport { LumencastApp } from \"./app.js\";\nimport { applyDelta } from \"./state/apply-delta.js\";\nimport { applySnapshot } from \"./state/apply-snapshot.js\";\nimport { createReservedLeafObserver } from \"./state/reserved-leaves.js\";\nimport { createStore } from \"./state/store.js\";\nimport { createBundleFetcher, type BundleFetcher, type RenderBundle } from \"./render/bundle.js\";\nimport { WsClient, type ConnectionStatus, type TransportError } from \"./transport/ws.js\";\nimport { validateOptions } from \"./internal/validate-options.js\";\nimport { addDiagnosticsHandler } from \"./render/diagnostics.js\";\nimport type { LumencastError, LumencastHandle, LumencastToken, MountOptions } from \"./types.js\";\n\nexport function mount(options: MountOptions): LumencastHandle {\n validateOptions(options);\n options.onStatus?.(\"disconnected\");\n\n const store = createStore();\n const baseUrl = deriveBaseUrl(options.serverUrl);\n // The render-bundle endpoint is auth-gated like the LSDP/1 WS. Resolve the\n // current session token per fetch (mirrors `setToken`) so each bundle GET\n // carries `Authorization: Bearer <token>`. `ws` is assigned below; the\n // closure runs only at fetch time, after assignment.\n const bundleFetcher = createBundleFetcher({\n baseUrl,\n ...(options.resolveBundleUrl !== undefined ? { resolveUrl: options.resolveBundleUrl } : {}),\n getAuthToken: () => ws.resolveCurrentToken(),\n });\n\n const bundleSignal = signal<RenderBundle | null>(null);\n const statusSignal = signal<ConnectionStatus>(\"disconnected\");\n const crossfadeKeySignal = signal<string>(\"__initial__\");\n\n const setStatus = (status: ConnectionStatus): void => {\n statusSignal.value = status;\n options.onStatus?.(status);\n };\n\n const reportError = (err: LumencastError): void => {\n options.onError?.(err);\n };\n\n let active = true;\n\n // Render-bundle versions already warmed (or warming) by the preload path.\n // Keeps both roster sources (the `scene_roster` frame and the `preloadRoster`\n // mount option) idempotent — a version is fetched by the warmer at most once.\n const warmedVersions = new Set<string>();\n\n // ADR Blue 009 §3.2–3.3 — surface the reserved `__cam.*` LSDP leaves (the\n // slot→peer assignments + the receive-only viewer creds) to the host so its\n // WebRTC viewer (Solar) can drive room joins + `x-zab.meet-peer` slot re-keying.\n // The runtime never joins, holds creds, or re-keys ; it only forwards. Created\n // only when the host opts in — zero cost on the preview/headless paths.\n const reservedLeaves = options.onReservedLeaves\n ? createReservedLeafObserver(options.onReservedLeaves)\n : undefined;\n\n // ADR 001 §3.4 (issue #34) — anti-silent-drop diagnostics are events\n // surfaced to the host, never console logs in `broadcast` mode.\n const removeDiagnosticsHandler = options.onDiagnostic\n ? addDiagnosticsHandler(options.onDiagnostic)\n : undefined;\n\n const ws = new WsClient({\n url: options.serverUrl,\n token: options.token,\n ...(options.scene !== undefined ? { scene: options.scene } : {}),\n ...(options.testSession !== undefined ? { session: options.testSession } : {}),\n onStatus: setStatus,\n onSnapshot: (frame) => {\n if (!active) return;\n reservedLeaves?.onSnapshot(frame.state);\n void onSnapshot(\n bundleFetcher,\n bundleSignal,\n crossfadeKeySignal,\n frame.scene_id,\n frame.scene_version,\n () => applySnapshot(store, frame),\n reportError,\n );\n options.onMetric?.({\n name: \"snapshot_received\",\n scene_id: frame.scene_id,\n path_count: Object.keys(frame.state).length,\n });\n },\n onDelta: (frame) => {\n if (!active) return;\n const start = performance.now();\n applyDelta(store, frame);\n reservedLeaves?.onDelta(frame.patches);\n options.onMetric?.({\n name: \"delta_applied\",\n duration_ms: performance.now() - start,\n });\n options.onMetric?.({ name: \"delta_received\", count: 1, path_count: frame.patches.length });\n },\n onSceneChanged: (frame) => {\n if (!active) return;\n // The fresh snapshot that follows is the source of truth — it carries\n // the new scene_version, drives the bundle fetch, and flips the\n // crossfade key. Nothing eager to do here.\n options.onMetric?.({\n name: \"scene_changed\",\n from: bundleSignal.value?.scene_version ?? null,\n to: frame.scene_version,\n });\n },\n onSceneRoster: (frame) => {\n if (!active) return;\n // The server advertised the show roster (LSDP/1.1 `scene_roster`).\n // Warm every scene's render bundle in the background so the first switch\n // to each is a cache hit, not a blocking fetch.\n warmRoster(frame.entries, \"frame\");\n },\n onServerError: (frame) => {\n reportError({\n code: frame.code,\n message: frame.message,\n recoverable: frame.recoverable,\n });\n },\n onTransportError: (err) => {\n reportError(transportToLumencastError(err));\n },\n });\n\n ws.start();\n\n // Public preload surface (#87b) — warm a host-supplied roster right after\n // mount, before any switch. Same warmer + cache as the `scene_roster` frame.\n if (options.preloadRoster !== undefined && options.preloadRoster.length > 0) {\n warmRoster(options.preloadRoster, \"option\");\n }\n\n const root: Root = createRoot(options.target);\n root.render(\n createElement(LumencastApp, {\n mode: options.mode,\n store,\n bundleSignal,\n statusSignal,\n crossfadeKeySignal,\n sendInput: (patches) => ws.sendInput(patches),\n // ADR 004 §A1.3 — thread the host capture resolver to the runtime context\n // so the `x-zab.capture` primitive's ACQUIRE mode can pin a device.\n ...(options.resolveCaptureDevice !== undefined\n ? { resolveCaptureDevice: options.resolveCaptureDevice }\n : {}),\n // ADR 006 #4 — thread the host peer-stream resolver (supplied by the\n // WebRTC viewer #3) so the `media` primitive's LIVE mode can render a\n // peer's MediaStream in `srcObject`.\n ...(options.resolvePeerStream !== undefined\n ? { resolvePeerStream: options.resolvePeerStream }\n : {}),\n // ADR 006 #3 — reactive variant : the LIVE `media` node re-renders when a\n // peer connects/leaves mid-show. `createPeerViewer()` supplies it.\n ...(options.subscribePeerStream !== undefined\n ? { subscribePeerStream: options.subscribePeerStream }\n : {}),\n }),\n );\n\n return {\n disconnect() {\n if (!active) return;\n active = false;\n removeDiagnosticsHandler?.();\n ws.close();\n root.unmount();\n },\n setToken(token: LumencastToken) {\n if (!active) return;\n ws.setToken(token);\n },\n };\n\n // --- helpers ----------------------------------------------------------\n\n /**\n * Warm the render bundles for a set of roster entries in the background.\n * Best-effort and non-blocking: each `get()` populates the fetcher's cache\n * keyed by `scene_version`, so the eventual `onSnapshot` fetch for that scene\n * is an instant cache hit. Idempotent via `warmedVersions`; the already-active\n * scene is skipped (its bundle is already loaded or in flight). A failed warm\n * is swallowed and its version released so a later roster can retry — the\n * scene still fetches on demand at switch time.\n */\n function warmRoster(\n entries: readonly { scene_id: string; scene_version: string }[],\n source: \"frame\" | \"option\",\n ): void {\n const activeVersion = bundleSignal.value?.scene_version;\n for (const { scene_id, scene_version } of entries) {\n if (scene_version === activeVersion) continue;\n if (warmedVersions.has(scene_version)) continue;\n warmedVersions.add(scene_version);\n void bundleFetcher\n .get(scene_id, scene_version)\n .then(() => {\n if (!active) return;\n options.onMetric?.({\n name: \"roster_preloaded\",\n scene_id,\n scene_version,\n source,\n });\n })\n .catch(() => {\n // Release so a subsequent roster advertisement can retry the warm.\n warmedVersions.delete(scene_version);\n });\n }\n }\n\n async function onSnapshot(\n fetcher: BundleFetcher,\n bSignal: typeof bundleSignal,\n cSignal: typeof crossfadeKeySignal,\n sceneId: string,\n sceneVersion: string,\n applyState: () => void,\n onErr: (err: LumencastError) => void,\n ): Promise<void> {\n let bundle: RenderBundle;\n try {\n bundle = await fetcher.get(sceneId, sceneVersion);\n } catch (err) {\n onErr({\n code: \"BUNDLE_FETCH_FAILED\",\n message: err instanceof Error ? err.message : \"render bundle fetch failed\",\n recoverable: true,\n });\n return;\n }\n if (!active) return;\n applyState();\n bSignal.value = bundle;\n cSignal.value = `${sceneId}::${sceneVersion}`;\n }\n}\n\nfunction transportToLumencastError(err: TransportError): LumencastError {\n return {\n code: err.code,\n message: err.message,\n recoverable: err.recoverable,\n };\n}\n\nfunction deriveBaseUrl(wsUrl: string): string {\n // wss://<host>/lsdp/v1 → https://<host>\n // ws://<host>/lsdp/v1 → http://<host>\n try {\n const u = new URL(wsUrl);\n const httpScheme = u.protocol === \"wss:\" ? \"https:\" : \"http:\";\n return `${httpScheme}//${u.host}`;\n } catch {\n return \"\";\n }\n}\n","// Per-primitive prop allowlists (ADR 001 §3.4 D4, issue #34).\n//\n// Each primitive declares the exact set of resolved-prop keys it\n// consumes at render time. Any prop reaching the renderer outside the\n// allowlist — whether from a compiled bundle, a hand-rolled RenderNode\n// or a binding key — produces a structured diagnostic (never a silent\n// drop). Values are NEVER inspected nor reported (R9) : the check is\n// purely key-based.\n//\n// Key-based is sufficient for live deltas too : an LSDP delta can only\n// change the VALUE behind an already-declared binding key\n// (`resolveProps`, tree.tsx) — it can never introduce a new prop key.\n// The per-node key set is therefore static, and the check runs once per\n// RenderNode object (WeakSet dedup) instead of once per render.\n//\n// These sets mirror what each primitive's component ACTUALLY reads\n// today. Spec'd fields the renderer does not consume yet (e.g. `text`\n// `format`, `stack` `padding`) are deliberately NOT listed : per the\n// anti-silent-drop policy they must warn until they are implemented.\n\nimport type { RenderKind, RenderNode } from \"./bundle\";\nimport { emitDiagnostic } from \"./diagnostics\";\n\n/** Universal props consumed by the Tree renderer itself\n * (`UniversalWrapper`, LSML 1.1 §5.4) on every primitive.\n *\n * ADR 002 §3.1 (D1) — `x`/`y` (compiler-flattened LSML `position:{x,y}`)\n * and `width`/`height` (flattened `size:{w,h}`) are consumed universally\n * for absolute placement : the Tree reads them into the wrapper's\n * `position`/`size`, so they are honoured on EVERY primitive, not\n * silently dropped. Frame additionally reads them into its own absolute\n * box (it lists them explicitly below too — the union is harmless). */\nconst UNIVERSAL_PROPS = [\n \"visible\",\n \"opacity\",\n \"universal_opacity\",\n \"rotation\",\n \"sizing\",\n \"x\",\n \"y\",\n \"width\",\n \"height\",\n // ADR 002 §3.2 (D2 / #D) — `blendMode` is consumed universally by the\n // wrapper (→ CSS `mix-blend-mode`) on every primitive.\n \"blendMode\",\n // ADR 002 §3.2 (#E) — a typed `mask` is lowered onto EVERY primitive by the\n // compiler and consumed by the Tree (built into a `<mask>` SVG element).\n \"mask\",\n] as const;\n\nfunction allow(keys: readonly string[]): ReadonlySet<string> {\n return new Set([...UNIVERSAL_PROPS, ...keys]);\n}\n\n/** Resolved-prop keys consumed per primitive (component + wrapper). */\nexport const PRIMITIVE_PROP_ALLOWLIST: Readonly<Record<RenderKind, ReadonlySet<string>>> = {\n stack: allow([\"direction\", \"gap\", \"wrap\", \"crossGap\", \"align\", \"justify\"]),\n grid: allow([\"cols\", \"rows\", \"gap\"]),\n frame: allow([\n \"x\",\n \"y\",\n \"width\",\n \"height\",\n \"scale\",\n \"rotate\",\n \"background\",\n \"backgrounds\",\n \"clipsContent\",\n ]),\n text: allow([\n \"value\",\n \"size\",\n \"font\",\n \"weight\",\n \"colour\",\n \"align\",\n \"lineHeight\",\n \"letterSpacing\",\n \"textTransform\",\n \"textDecoration\",\n \"fontStyle\",\n \"maxLines\",\n ]),\n image: allow([\"src\", \"alt\", \"fit\", \"position\", \"width\", \"height\"]),\n shape: allow([\n \"geometry\",\n \"kind\",\n \"width\",\n \"height\",\n \"radius\",\n \"fill\",\n \"fills\",\n \"stroke\",\n \"stroke_width\",\n \"strokes\",\n \"pathData\",\n \"paths\",\n \"ariaLabel\",\n ]),\n // `peerLabel` (ADR 006 #4) selects the live MediaStream mode : a node whose\n // source is a `meet.peer.peer_label` is rendered in `srcObject` from a host\n // resolver instead of `<video src>`. Listed so it is NOT flagged as a silent\n // drop by the anti-drop audit when a scene carries a live source.\n media: allow([\"src\", \"peerLabel\", \"loop\", \"mute\", \"autoplay\", \"fit\"]),\n // ADR 006 §3.3/§3.5 — the unified source kind. `peer_label` is the stream\n // reference (resolved to a MediaStream → srcObject) ; `object_fit`/`muted`\n // drive the video ; `x-zab.sourceKind` is advisory ; `metadata` carries the\n // editor round-trip (figma). Geometry is universal as flat `x/y/width/height`,\n // but an UNCOMPILED from-scene node carries the NESTED `position`/`size` shape\n // (the Tree flattens it as a fallback) — listed so neither form is flagged as\n // a silent drop by the anti-drop audit.\n \"meet.peer\": allow([\n \"peer_label\",\n \"object_fit\",\n \"muted\",\n \"x-zab.sourceKind\",\n \"metadata\",\n \"position\",\n \"size\",\n ]),\n instance: allow([\"scene_id\", \"scene_version\", \"size\", \"position\"]),\n // RFC-0001 / ADR 004 — vendor capture placeholder. `width`/`height` are the\n // flattened geometry (universal) ; the `x-zab.*` props are carried as\n // metadata (the renderer reserves the box, ignores deviceRef). Listed so\n // they are NOT flagged as silent drops by the anti-drop audit.\n \"x-zab.capture\": allow([\"x-zab.sourceKind\", \"x-zab.deviceRef\", \"width\", \"height\"]),\n // ADR Blue 009 §3.1 (Amendment 2) — vendor meet-peer SLOT placeholder.\n // `width`/`height` are the flattened geometry (universal) ; `x-zab.slotRef`\n // is the logical slot identity carried as metadata (the runtime resolves\n // `slotRef → peer_label` from stream-level ZabCam state). NO cam/peer\n // identity is carried. Listed so they are NOT flagged as silent drops.\n \"x-zab.meet-peer\": allow([\"x-zab.slotRef\", \"width\", \"height\"]),\n // `repeat` is dispatched specially by the tree ; its only consumed\n // binding is `items`.\n repeat: new Set([\"items\"]),\n};\n\nfunction isAllowed(kind: RenderKind, key: string): boolean {\n const allowed = PRIMITIVE_PROP_ALLOWLIST[kind];\n if (allowed === undefined) return true; // unknown kind warns separately (tree.tsx)\n if (allowed.has(key)) return true;\n // `instance` exposes bound sub-scene parameters under `params.*`\n // (LSML §4.9) — the whole namespace is part of its contract.\n if (kind === \"instance\" && (key === \"params\" || key.startsWith(\"params.\"))) return true;\n return false;\n}\n\n// One check per RenderNode object — bundles are immutable once fetched,\n// and a node's key set cannot change live (see module header).\nconst checkedNodes = new WeakSet<RenderNode>();\n\n/**\n * Audit a node's static props + binding keys against its primitive's\n * allowlist. Every unknown key emits ONE structured diagnostic naming\n * `node.id` + the prop (never the value, R9). Idempotent per node.\n */\nexport function checkNodeProps(node: RenderNode): void {\n if (checkedNodes.has(node)) return;\n checkedNodes.add(node);\n const keys = new Set<string>([\n ...Object.keys(node.props ?? {}),\n ...Object.keys(node.bindings ?? {}),\n ]);\n for (const key of keys) {\n if (!isAllowed(node.kind, key)) {\n emitDiagnostic(\n node.id,\n `${node.kind}.${key}`,\n \"is not consumed by this primitive's renderer ; the prop is ignored (anti-silent-drop, ADR 001 §3.4)\",\n );\n }\n }\n}\n","// WebRTC viewer (mesh, VIEWER role) — ADR 006 §3.3 (C1), issue #3.\n//\n// A viewer-only port of `Prism/src/renderer/src/lib/meet-client.ts` (§1.4) : it\n// joins a Meet room, receives N peers and exposes one `MediaStream` per peer.\n// It NEVER publishes : no `getUserMedia`, no local stream, every transceiver is\n// `recvonly`. Recovering a WebRTC flow needs no capture permission (ADR §2),\n// which is the whole point of the pivot — Solar-CEF (on-air) and the Prism\n// return webview can both be viewers without a capture grant.\n//\n// It reuses the reference's hard-won mesh logic verbatim in spirit :\n// - perfect-negotiation (polite/impolite glare handling) ;\n// - symmetric m-line ordering (audio transceiver first, video second) ;\n// - STUN-first + per-URL TURN iceServers ;\n// - one aggregated `MediaStream` per peer (`track` → addTrack, `ended` →\n// removeTrack), handed to the consumer as `RemoteTrackEvent`.\n//\n// OWNERSHIP : the viewer owns each peer's `RTCPeerConnection` AND the\n// `MediaStream` it aggregates. It is the SOLE authority on the track lifecycle —\n// created on `track`, removed on `ended` / `peer-left` / `connectionstatechange\n// failed|closed`, all torn down on `leave()`. A downstream `<video srcObject>`\n// (the `media` primitive #4) is a pure consumer : unmounting it clears its own\n// `srcObject` and stops nothing. A returning mirror can never kill a peer for\n// the on-air composite.\n//\n// PEER LABEL : the transverse invariant gravé by Conduit (ZabCam contract #6) is\n// a strict STRING equality — `peer_label == MeetClientOptions.name ==\n// RemoteTrackEvent.peerName`, all matching `^[a-z][a-z0-9_-]{0,63}$`. Publishers\n// (#5 Prism / #2 Pulsar) join with `name = peer_label` ; it comes back on the\n// remote side as `PeerInfo.name`, surfaced verbatim as `RemoteTrackEvent.peerName`.\n// The viewer therefore resolves `peer_label → MediaStream` by indexing peers by\n// `peerName` (== label), NEVER by the opaque `peerId`. No separate label channel\n// is needed : the join announce already carries it.\n\nexport type PeerRole = \"publisher\" | \"viewer\";\n\nexport interface PeerInfo {\n id: string;\n /** The peer's join name — the STABLE `peer_label` (ZabCam contract #6). */\n name: string;\n role: PeerRole;\n}\n\nexport type SignalPayload =\n | {\n kind: \"sdp\";\n description: { type: \"offer\" | \"answer\" | \"pranswer\" | \"rollback\"; sdp: string };\n }\n | {\n kind: \"ice\";\n candidate: {\n candidate: string;\n sdpMid: string | null;\n sdpMLineIndex: number | null;\n usernameFragment?: string | null;\n };\n }\n | { kind: \"control\"; event: \"screen-start\" | \"screen-stop\" | \"mute\" | \"unmute\" };\n\nexport type ClientMessage =\n | { type: \"join\"; name: string; role?: PeerRole }\n | { type: \"signal\"; to: string; payload: SignalPayload }\n | { type: \"leave\" };\n\nexport type ServerMessage =\n | {\n type: \"joined\";\n peerId: string;\n roomId: string;\n role: PeerRole;\n peers: PeerInfo[];\n turn: { urls: string[]; username: string; credential: string; ttl: number };\n }\n | { type: \"peer-joined\"; peer: PeerInfo }\n | { type: \"peer-left\"; peerId: string }\n | { type: \"signal\"; from: string; payload: SignalPayload }\n | { type: \"error\"; code: string; message: string };\n\nexport interface RemoteTrackEvent {\n peerId: string;\n /** The peer's join name == its STABLE `peer_label` (Conduit invariant #6 :\n * `peer_label == name == peerName`, strict string equality). This is the key\n * a `meet.peer` LSML node's `peerLabel` resolves against — never `peerId`. */\n peerName: string;\n stream: MediaStream;\n}\n\ntype EventMap = {\n joined: { peerId: string; peers: PeerInfo[] };\n \"peer-joined\": PeerInfo;\n \"peer-left\": { peerId: string; peerName: string };\n \"remote-track\": RemoteTrackEvent;\n \"connection-state\": { peerId: string; state: RTCPeerConnectionState };\n error: { code: string; message: string };\n close: { code: number; reason: string };\n};\n\ntype Listener<K extends keyof EventMap> = (event: EventMap[K]) => void;\n\ninterface RemoteState {\n info: PeerInfo;\n pc: RTCPeerConnection;\n stream: MediaStream;\n makingOffer: boolean;\n ignoreOffer: boolean;\n pendingCandidates: RTCIceCandidateInit[];\n}\n\n/** Minimal injectable factory for the WebSocket + RTCPeerConnection, so the\n * viewer is testable without a real browser stack. Defaults to the globals. */\nexport interface MeetViewerDeps {\n WebSocket: typeof WebSocket;\n RTCPeerConnection: typeof RTCPeerConnection;\n MediaStream: typeof MediaStream;\n}\n\nexport interface MeetViewerOptions {\n signalingUrl: string;\n roomId: string;\n /** Room/viewer token, sent as a query param to the signaling WS. */\n token: string;\n /** This viewer's announce name on the mesh (it does not publish a stream). */\n name: string;\n deps?: Partial<MeetViewerDeps>;\n}\n\nexport class MeetViewer {\n private ws: WebSocket | null = null;\n private remotes = new Map<string, RemoteState>();\n private iceServers: RTCIceServer[] = [];\n private selfId: string | null = null;\n private listeners = new Map<keyof EventMap, Set<Listener<never>>>();\n private readonly deps: MeetViewerDeps;\n\n constructor(private readonly options: MeetViewerOptions) {\n this.deps = {\n WebSocket: options.deps?.WebSocket ?? globalThis.WebSocket,\n RTCPeerConnection: options.deps?.RTCPeerConnection ?? globalThis.RTCPeerConnection,\n MediaStream: options.deps?.MediaStream ?? globalThis.MediaStream,\n };\n }\n\n on<K extends keyof EventMap>(type: K, listener: Listener<K>): () => void {\n const set = this.listeners.get(type) ?? new Set<Listener<never>>();\n this.listeners.set(type, set);\n set.add(listener as Listener<never>);\n return () => set.delete(listener as Listener<never>);\n }\n\n /** Join the room as a VIEWER (recvonly). No capture, no publish. */\n join(): Promise<void> {\n return this.openSocket();\n }\n\n /** Leave and tear down every peer connection + aggregated stream. As the\n * track owner, this is where the streams (and the device-side tracks) end. */\n leave(): void {\n this.send({ type: \"leave\" });\n this.ws?.close(1000, \"viewer-leave\");\n }\n\n /* ---- Socket ------------------------------------------------------- */\n\n private openSocket(): Promise<void> {\n const url = new URL(this.options.signalingUrl);\n url.searchParams.set(\"room\", this.options.roomId);\n url.searchParams.set(\"token\", this.options.token);\n\n return new Promise((resolve, reject) => {\n const ws = new this.deps.WebSocket(url.toString());\n this.ws = ws;\n\n const onOpen = () => {\n ws.removeEventListener(\"error\", onError);\n // role:\"viewer\" — the signaling server allocates no publish slot.\n this.send({ type: \"join\", name: this.options.name, role: \"viewer\" });\n resolve();\n };\n const onError = (event: Event) => {\n ws.removeEventListener(\"open\", onOpen);\n reject(event);\n };\n\n ws.addEventListener(\"open\", onOpen, { once: true });\n ws.addEventListener(\"error\", onError, { once: true });\n ws.addEventListener(\"message\", (ev) => void this.onMessage((ev as MessageEvent).data));\n ws.addEventListener(\"close\", (ev) => {\n this.tearDown();\n this.emit(\"close\", { code: (ev as CloseEvent).code, reason: (ev as CloseEvent).reason });\n });\n });\n }\n\n private send(msg: ClientMessage): void {\n if (this.ws && this.ws.readyState === this.deps.WebSocket.OPEN) {\n this.ws.send(JSON.stringify(msg));\n }\n }\n\n private tearDown(): void {\n for (const r of this.remotes.values()) r.pc.close();\n this.remotes.clear();\n }\n\n /* ---- Protocol ----------------------------------------------------- */\n\n private async onMessage(raw: unknown): Promise<void> {\n let msg: ServerMessage;\n try {\n msg = JSON.parse(String(raw)) as ServerMessage;\n } catch {\n return;\n }\n\n switch (msg.type) {\n case \"joined\": {\n this.selfId = msg.peerId;\n // TURN ONLY (no STUN). This viewer runs in an Electron <webview> whose\n // P2P stack has NO mDNS resolver, so host `.local` candidates never\n // resolve (-105), and it can't resolve STUN *hostnames* either\n // (stun.l.google.com / stun.cloudflare.com → -105). The only viable\n // path is the relay, and the server now advertises TURN by IP\n // (turn:51.91.126.43:3478), which needs no DNS. Dropping the unusable\n // STUN hostnames removes the -105 noise and the dead srflx gathering.\n this.iceServers = msg.turn.urls.map((url) => ({\n urls: url,\n username: msg.turn.username,\n credential: msg.turn.credential,\n }));\n this.emit(\"joined\", { peerId: msg.peerId, peers: msg.peers });\n for (const peer of msg.peers) this.ensureRemote(peer);\n break;\n }\n case \"peer-joined\": {\n this.emit(\"peer-joined\", msg.peer);\n this.ensureRemote(msg.peer);\n break;\n }\n case \"peer-left\": {\n const remote = this.remotes.get(msg.peerId);\n if (remote) {\n // The pc owns the tracks — closing it ends them. The registry/consumer\n // are notified via the peer-left event (label-keyed).\n remote.pc.close();\n this.remotes.delete(msg.peerId);\n this.emit(\"peer-left\", { peerId: msg.peerId, peerName: remote.info.name });\n }\n break;\n }\n case \"signal\": {\n await this.handleSignal(msg.from, msg.payload);\n break;\n }\n case \"error\": {\n this.emit(\"error\", { code: msg.code, message: msg.message });\n break;\n }\n }\n }\n\n private async handleSignal(from: string, payload: SignalPayload): Promise<void> {\n let remote = this.remotes.get(from);\n if (!remote) {\n remote = this.ensureRemote({ id: from, name: from.slice(0, 8), role: \"publisher\" });\n }\n const { pc } = remote;\n\n if (payload.kind === \"sdp\") {\n const desc = payload.description;\n const offerCollision =\n desc.type === \"offer\" && (remote.makingOffer || pc.signalingState !== \"stable\");\n remote.ignoreOffer = !this.isPolite(from) && offerCollision;\n if (remote.ignoreOffer) return;\n\n await pc.setRemoteDescription(desc);\n for (const c of remote.pendingCandidates) {\n try {\n await pc.addIceCandidate(c);\n } catch {\n /* ignore late/stale candidates */\n }\n }\n remote.pendingCandidates = [];\n\n if (desc.type === \"offer\") {\n await pc.setLocalDescription();\n if (pc.localDescription) {\n this.sendSignal(from, {\n kind: \"sdp\",\n description: {\n type: pc.localDescription.type as \"offer\" | \"answer\" | \"pranswer\" | \"rollback\",\n sdp: pc.localDescription.sdp,\n },\n });\n }\n }\n return;\n }\n\n if (payload.kind === \"ice\") {\n const init = payload.candidate;\n if (pc.remoteDescription) {\n try {\n await pc.addIceCandidate(init);\n } catch (err) {\n if (!remote.ignoreOffer) throw err;\n }\n } else {\n remote.pendingCandidates.push(init);\n }\n return;\n }\n // control payloads ignored — the viewer does not act on screen/mute hints.\n }\n\n /* ---- Peer setup --------------------------------------------------- */\n\n private ensureRemote(peer: PeerInfo): RemoteState {\n const existing = this.remotes.get(peer.id);\n if (existing) return existing;\n\n // relay-only: in the Electron <webview> host (.local) and srflx candidates\n // are unusable (no mDNS resolver, STUN hostnames unresolvable), so force the\n // ICE agent to gather/use ONLY relay candidates via the by-IP TURN server.\n // The publisher (browser) gathers all transports incl. relay, so the\n // relay↔relay pair connects. Avoids ICE stalling on dead host/srflx checks.\n const pc = new this.deps.RTCPeerConnection({\n iceServers: this.iceServers,\n iceTransportPolicy: \"relay\",\n });\n const stream = new this.deps.MediaStream();\n\n // VIEWER : both transceivers are recvonly and carry NO local track. Order\n // (audio first, video second) must match the publisher's so the m-lines\n // line up — the same invariant as the reference.\n const audioTx = pc.addTransceiver(\"audio\", { direction: \"recvonly\" });\n const videoTx = pc.addTransceiver(\"video\", { direction: \"recvonly\" });\n\n // BUNDLE codec-collision fix (ADR 006 #3). A recvonly viewer offering the\n // FULL Chromium codec catalogue (VP8/VP9×profiles/H264×profiles/AV1/H265 +\n // rtx/red/ulpfec/flexfec, audio opus + telephone-event) maximises payload-\n // type pressure. Under BUNDLE all m-lines share ONE PT namespace, so when\n // the publisher (answerer, with pre-allocated sendrecv transceivers — see\n // `meet-client.ts`) reconciles that dense offer, PTs collide ACROSS m-lines\n // (the observed `126` audio telephone-event vs `39` video H264). A pure\n // viewer needs ONE coherent codec per kind, not the whole catalogue : we\n // pin a deduplicated, minimal preference set so the offered PT space is\n // small and collision-free by construction. This is NOT SDP munging — it is\n // the spec'd `setCodecPreferences` API ; Chromium still owns PT assignment.\n pinViewerCodecs(audioTx, videoTx);\n\n const state: RemoteState = {\n info: peer,\n pc,\n stream,\n makingOffer: false,\n ignoreOffer: false,\n pendingCandidates: [],\n };\n\n pc.addEventListener(\"negotiationneeded\", () => {\n void (async () => {\n try {\n state.makingOffer = true;\n await pc.setLocalDescription();\n if (pc.localDescription) {\n this.sendSignal(peer.id, {\n kind: \"sdp\",\n description: {\n type: pc.localDescription.type as \"offer\" | \"answer\" | \"pranswer\" | \"rollback\",\n sdp: pc.localDescription.sdp,\n },\n });\n }\n } catch {\n /* transient — glare or mid-close */\n } finally {\n state.makingOffer = false;\n }\n })();\n });\n\n pc.addEventListener(\"icecandidate\", (ev) => {\n const candidate = (ev as RTCPeerConnectionIceEvent).candidate;\n if (!candidate) return;\n this.sendSignal(peer.id, {\n kind: \"ice\",\n candidate: {\n candidate: candidate.candidate,\n sdpMid: candidate.sdpMid,\n sdpMLineIndex: candidate.sdpMLineIndex,\n usernameFragment: candidate.usernameFragment,\n },\n });\n });\n\n pc.addEventListener(\"track\", (ev) => {\n const track = (ev as RTCTrackEvent).track;\n // Aggregate every incoming track into the peer's single MediaStream so\n // the consumer gets one coherent source for `<video srcObject>`.\n if (!state.stream.getTracks().includes(track)) {\n state.stream.addTrack(track);\n }\n track.addEventListener(\"ended\", () => {\n state.stream.removeTrack(track);\n });\n this.emit(\"remote-track\", {\n peerId: peer.id,\n peerName: peer.name,\n stream: state.stream,\n });\n });\n\n pc.addEventListener(\"connectionstatechange\", () => {\n this.emit(\"connection-state\", { peerId: peer.id, state: pc.connectionState });\n if (pc.connectionState === \"failed\" || pc.connectionState === \"closed\") {\n this.remotes.delete(peer.id);\n this.emit(\"peer-left\", { peerId: peer.id, peerName: peer.name });\n }\n });\n\n this.remotes.set(peer.id, state);\n return state;\n }\n\n /* ---- Helpers ------------------------------------------------------ */\n\n private isPolite(otherId: string): boolean {\n if (!this.selfId) return false;\n return this.selfId > otherId;\n }\n\n private sendSignal(to: string, payload: SignalPayload): void {\n this.send({ type: \"signal\", to, payload });\n }\n\n private emit<K extends keyof EventMap>(type: K, event: EventMap[K]): void {\n const set = this.listeners.get(type);\n if (!set) return;\n for (const listener of set) (listener as Listener<K>)(event);\n }\n}\n\n/* ---- Codec preference (BUNDLE collision fix) ----------------------- */\n\n/** Pin a minimal, deduplicated codec preference on a viewer's recvonly\n * transceivers so the offered payload-type space stays small and BUNDLE-safe.\n *\n * AUDIO : opus (+ keep telephone-event for spec completeness, it is harmless).\n * VIDEO : H264 + its rtx ONLY (drop VP9/AV1/H265 multi-profile clutter). H264\n * is what the publisher (Prism cam / Pulsar NVENC) emits ; a viewer\n * that only ever RECEIVES needs no broader set. rtx is kept so NACK\n * retransmission still works.\n *\n * Feature-detected end-to-end : if `setCodecPreferences` or\n * `RTCRtpReceiver.getCapabilities` is unavailable (older engines, jsdom/test\n * fakes), this is a silent no-op and the transceiver keeps Chromium's default\n * full list — behaviour is never WORSE than before the fix. */\nfunction pinViewerCodecs(audioTx: unknown, videoTx: unknown): void {\n const getCaps = (\n globalThis as {\n RTCRtpReceiver?: { getCapabilities?: (k: string) => RTCRtpCapabilities | null };\n }\n ).RTCRtpReceiver?.getCapabilities;\n if (typeof getCaps !== \"function\") return;\n\n pinKind(videoTx, getCaps(\"video\"), (mime) => {\n const m = mime.toLowerCase();\n // Keep H264 and the generic rtx (retransmission) codec only.\n return m === \"video/h264\" || m === \"video/rtx\";\n });\n pinKind(audioTx, getCaps(\"audio\"), (mime) => {\n const m = mime.toLowerCase();\n return m === \"audio/opus\" || m === \"audio/telephone-event\";\n });\n}\n\n/** Apply `setCodecPreferences` with the subset of `caps` whose mimeType passes\n * `keep`, preserving the platform's preferred order. Guarded : no transceiver,\n * no `setCodecPreferences`, no caps, or an empty filtered set → no-op. */\nfunction pinKind(\n tx: unknown,\n caps: RTCRtpCapabilities | null | undefined,\n keep: (mimeType: string) => boolean,\n): void {\n const setPrefs = (tx as { setCodecPreferences?: (codecs: RTCRtpCodec[]) => void } | null)\n ?.setCodecPreferences;\n if (typeof setPrefs !== \"function\" || !caps) return;\n const codecs = caps.codecs.filter((c) => keep(c.mimeType));\n if (codecs.length === 0) return; // never offer an empty m-line\n try {\n setPrefs.call(tx, codecs);\n } catch {\n // Some engines reject a preference list (e.g. rtx without its apt primary in\n // the same call) — fall back to the default full list rather than break the\n // transceiver. The fix is best-effort hardening, never a hard dependency.\n }\n}\n","// Peer-stream registry — the bridge between the WebRTC viewer (#3) and the\n// `media` primitive's LIVE mode (#4).\n//\n// The viewer feeds this registry one entry per CONNECTED peer, keyed by the\n// peer's STABLE `peer_label` (the ZabCam contract label #6, announced on the\n// mesh as the peer's join `name` — see `PeerInfo.name` / `RemoteTrackEvent`).\n// The `media` primitive resolves `peerLabel → MediaStream` through it.\n//\n// Reactivity : a peer's stream becomes available ASYNCHRONOUSLY (after the room\n// join + SDP/ICE), so a `media` node that mounted before the peer connected\n// must be notified when its stream arrives. The registry exposes both a\n// one-shot `resolvePeerStream` (the #4 contract, synchronous) AND a\n// `subscribePeerStream(label, cb)` push channel the LIVE primitive uses to\n// re-render on connect / disconnect. The store is a plain Map guarded by a\n// listener set — no signals dependency, so it adds nothing to the render path.\n//\n// OWNERSHIP (the #3↔#4 lifecycle decision) : the registry NEVER creates or\n// stops a track. It only HOLDS a reference to a `MediaStream` owned by the\n// viewer's `RTCPeerConnection`. The viewer is the sole authority on the track\n// lifecycle (created on `track`, removed on `ended` / `peer-left` / teardown).\n// A consuming `<video>` unmounting clears its own `srcObject` and nothing else\n// — it can never tear a peer down for the on-air composite.\n\nexport type PeerStreamListener = (stream: MediaStream | null) => void;\n\n/** Notified whenever the live roster changes ORDER (a peer connects for the first\n * time, or a connected peer drops). A pure re-`set` of an already-present label's\n * stream is NOT a roster change (same arrival order) — it reaches per-label\n * `subscribe` listeners only. Carries no payload : the listener re-reads\n * `orderedLabels()`. */\nexport type RosterListener = () => void;\n\nexport interface PeerStreamRegistry {\n /** #4 contract — the current stream for a label, or `null` if the peer is not\n * connected (yet / any more). Synchronous, side-effect free. */\n resolve(peerLabel: string): MediaStream | null;\n /** The live `peer_label`s in ARRIVAL ORDER (insertion order of the backing Map,\n * restricted to labels that currently hold a stream). Drives positional slot\n * resolution (`@<n>` → `orderedLabels()[n]`, ADR Blue 009 axe 1 positional\n * variant). Returns a fresh array — safe for the caller to keep / index. */\n orderedLabels(): string[];\n /** Push channel for the LIVE `media` primitive : invoked immediately with the\n * current value, then on every change for `peerLabel`. Returns an\n * unsubscribe. */\n subscribe(peerLabel: string, listener: PeerStreamListener): () => void;\n /** Roster-change channel : invoked whenever a peer connects (new label) or\n * leaves (label dropped) — i.e. whenever `orderedLabels()` could shift. Lets a\n * positional consumer re-resolve `@<n>` when arrivals/departures shuffle the\n * order. NOT invoked on a pure stream replacement of an existing label. Returns\n * an unsubscribe. */\n subscribeRoster(listener: RosterListener): () => void;\n /** Viewer-side : publish / replace a peer's stream (peer connected). */\n set(peerLabel: string, stream: MediaStream): void;\n /** Viewer-side : drop a peer's stream (peer left / connection failed). The\n * registry forgets the reference ; it does NOT stop the tracks (the viewer's\n * pc.close() does, as the track owner). */\n remove(peerLabel: string): void;\n /** Viewer-side : forget every entry (room teardown). Reference-only, no track\n * stops. */\n clear(): void;\n}\n\nexport function createPeerStreamRegistry(): PeerStreamRegistry {\n const streams = new Map<string, MediaStream>();\n const listeners = new Map<string, Set<PeerStreamListener>>();\n const rosterListeners = new Set<RosterListener>();\n\n function notify(peerLabel: string): void {\n const set = listeners.get(peerLabel);\n if (set === undefined) return;\n const value = streams.get(peerLabel) ?? null;\n for (const listener of set) listener(value);\n }\n\n function notifyRoster(): void {\n for (const listener of [...rosterListeners]) listener();\n }\n\n return {\n resolve(peerLabel) {\n return streams.get(peerLabel) ?? null;\n },\n orderedLabels() {\n // Map preserves insertion order = arrival order ; every key holds a stream\n // (a dropped peer is `delete`d in remove()).\n return [...streams.keys()];\n },\n subscribeRoster(listener) {\n rosterListeners.add(listener);\n return () => {\n rosterListeners.delete(listener);\n };\n },\n subscribe(peerLabel, listener) {\n let set = listeners.get(peerLabel);\n if (set === undefined) {\n set = new Set();\n listeners.set(peerLabel, set);\n }\n set.add(listener);\n // Emit the current value synchronously so a late subscriber sees an\n // already-connected peer without waiting for the next change.\n listener(streams.get(peerLabel) ?? null);\n return () => {\n const s = listeners.get(peerLabel);\n if (s === undefined) return;\n s.delete(listener);\n if (s.size === 0) listeners.delete(peerLabel);\n };\n },\n set(peerLabel, stream) {\n if (streams.get(peerLabel) === stream) return; // idempotent re-emit guard\n const isNew = !streams.has(peerLabel); // a brand-new arrival shifts the roster\n streams.set(peerLabel, stream);\n notify(peerLabel);\n if (isNew) notifyRoster();\n },\n remove(peerLabel) {\n if (!streams.has(peerLabel)) return;\n streams.delete(peerLabel);\n notify(peerLabel);\n notifyRoster(); // a departure shifts every later position\n },\n clear() {\n const labels = [...streams.keys()];\n streams.clear();\n for (const label of labels) notify(label);\n if (labels.length > 0) notifyRoster();\n },\n };\n}\n","// WebRTC viewer — public surface (ADR 006 §3.3, issue #3).\n//\n// `createPeerViewer` wires a `MeetViewer` (mesh, viewer role) to a\n// `PeerStreamRegistry` and returns the `resolvePeerStream` / `subscribePeerStream`\n// the `media` primitive's LIVE mode (#4) consumes via `MountOptions`. This is\n// the #3↔#4 bridge : the viewer receives peers, the registry maps\n// `peer_label → MediaStream`, the primitive renders it in `<video srcObject>`.\n//\n// MULTI-ROOM (final model) : `createMultiRoomPeerViewer({ rooms: [...] })` joins\n// EVERY room with its own mesh and feeds ONE shared registry, so the `meet.peer`\n// renderer resolves a `peer_label` to its stream whatever room it came from.\n\nimport { MeetViewer, type MeetViewerOptions, type RemoteTrackEvent } from \"./meet-viewer.js\";\nimport {\n createPeerStreamRegistry,\n type PeerStreamListener,\n type PeerStreamRegistry,\n} from \"./peer-stream-registry.js\";\n\nexport {\n MeetViewer,\n type MeetViewerOptions,\n type MeetViewerDeps,\n type PeerInfo,\n type RemoteTrackEvent,\n} from \"./meet-viewer.js\";\nexport {\n createPeerStreamRegistry,\n type PeerStreamRegistry,\n type PeerStreamListener,\n type RosterListener,\n} from \"./peer-stream-registry.js\";\n\nexport interface PeerViewer {\n /** Join the room(s) (viewer role, no capture). */\n join: () => Promise<void>;\n /** Leave + tear down all peer connections (the track owner releases here). */\n leave: () => void;\n /** #4 contract — pass to `mount({ resolvePeerStream })`. Synchronous. */\n resolvePeerStream: (peerLabel: string) => MediaStream | null;\n /** Push channel for the LIVE primitive to re-render on connect/disconnect. */\n subscribePeerStream: (peerLabel: string, listener: PeerStreamListener) => () => void;\n /** The underlying registry, for advanced hosts. */\n registry: PeerStreamRegistry;\n /** Single-room only — the underlying mesh, for diagnostics. Absent in the\n * multi-room viewer (it owns N meshes). */\n viewer?: MeetViewer;\n}\n\n/** Feed a (possibly shared) registry from ONE viewer's lifecycle. A label is\n * owned by the FIRST room that connects it (`label-collision` policy) : `claim`\n * decides whether this viewer may publish/withdraw a given label, so a second\n * room carrying the same `peer_label` never clobbers the first. Returns the\n * viewer + a `dispose` that closes the mesh and releases this viewer's labels. */\n/** Normalise a peer name / peer_label into the SAME key both sides agree on.\n * The publisher announces a FREE name on the mesh (e.g. \"Publisher 366\"), but\n * the scene's `peer_label` is slugified at LSML export (from-scene → \"publisher\n * _366\", per source-node.ts `slugifyToLabel`). Strict `peerName === peer_label`\n * therefore never matches. Applying the SAME slug to BOTH the indexing side\n * (peerName) and the resolution side (peer_label) makes the map work regardless\n * of format (a raw label and its slug collapse to the same key). Mirrors\n * Prism `slugifyToLabel`. */\nexport function labelKey(s: string): string {\n return s\n .toLowerCase()\n .replace(/[^a-z0-9_-]+/g, \"_\")\n .replace(/^[_-]+|[_-]+$/g, \"\");\n}\n\nfunction wireViewer(\n viewer: MeetViewer,\n registry: PeerStreamRegistry,\n claim: {\n acquire: (label: string, viewer: MeetViewer) => boolean;\n release: (label: string, viewer: MeetViewer) => void;\n },\n): void {\n // Index the registry by the NORMALISED label (slug of peerName), so it matches\n // the scene node's slugified `peer_label`. Never the opaque `peerId`.\n viewer.on(\"remote-track\", (e: RemoteTrackEvent) => {\n const key = labelKey(e.peerName);\n if (claim.acquire(key, viewer)) registry.set(key, e.stream);\n });\n viewer.on(\"peer-left\", (e) => {\n const key = labelKey(e.peerName);\n if (claim.acquire(key, viewer)) {\n registry.remove(key);\n claim.release(key, viewer);\n }\n });\n}\n\n/** Single-room ownership : trivially, this lone viewer owns every label. */\nconst SOLE_OWNER = {\n acquire: () => true,\n release: () => {},\n};\n\n/** Build a viewer + registry for a SINGLE room and expose the #4 resolver.\n * (Back-compat surface — `createMultiRoomPeerViewer` is the final model.) */\nexport function createPeerViewer(options: MeetViewerOptions): PeerViewer {\n const registry = createPeerStreamRegistry();\n const viewer = new MeetViewer(options);\n wireViewer(viewer, registry, SOLE_OWNER);\n return {\n join: () => viewer.join(),\n leave: () => {\n viewer.leave();\n registry.clear();\n },\n resolvePeerStream: (peerLabel) => registry.resolve(labelKey(peerLabel)),\n subscribePeerStream: (peerLabel, listener) => registry.subscribe(labelKey(peerLabel), listener),\n registry,\n viewer,\n };\n}\n\n/** A single room's connection params (one mesh per entry). */\nexport type RoomOptions = Omit<MeetViewerOptions, \"name\"> & {\n /** This viewer's announce name on the mesh. Defaults to \"solar-viewer\". */\n name?: string;\n};\n\nexport interface MultiRoomPeerViewerOptions {\n rooms: RoomOptions[];\n /** Shared deps (WS/RTCPeerConnection/MediaStream) applied to every room when a\n * room entry does not override them — used to test without a browser stack. */\n deps?: MeetViewerOptions[\"deps\"];\n}\n\nexport interface MultiRoomPeerViewer extends PeerViewer {\n /** Reconcile the live room set : open meshes for new rooms, close meshes for\n * removed ones (matched by `roomId`). Best-effort ; idempotent for an\n * unchanged set. Newly opened rooms are joined immediately. */\n setRooms: (rooms: RoomOptions[]) => Promise<void>;\n}\n\n/** The FINAL viewer model : join N rooms, aggregate every peer into ONE shared\n * registry keyed by `peer_label`. The `meet.peer` renderer resolves a label to\n * its stream regardless of which room the peer published in.\n *\n * LABEL COLLISION (improbable at the POC) : FIRST-CONNECTED-WINS. The room that\n * first connects a `peer_label` owns it ; a second room carrying the same label\n * is ignored until the owner releases it (on the owner's `peer-left`). This is\n * deterministic and never flips a live source under the compositor.\n *\n * LIFECYCLE : one mesh per room ; `leave()` closes all. `setRooms()` reconciles\n * on re-arm (close removed rooms, open new ones), matched by `roomId`. */\nexport function createMultiRoomPeerViewer(\n options: MultiRoomPeerViewerOptions,\n): MultiRoomPeerViewer {\n const registry = createPeerStreamRegistry();\n // roomId → { viewer, joined }\n const meshes = new Map<string, { viewer: MeetViewer }>();\n // peer_label → owning viewer (first-connected-wins).\n const owners = new Map<string, MeetViewer>();\n\n const claim = {\n acquire: (label: string, viewer: MeetViewer): boolean => {\n const owner = owners.get(label);\n if (owner === undefined) {\n owners.set(label, viewer);\n return true;\n }\n return owner === viewer; // only the owning room may publish/withdraw\n },\n release: (label: string, viewer: MeetViewer): void => {\n if (owners.get(label) === viewer) owners.delete(label);\n },\n };\n\n function openRoom(room: RoomOptions): void {\n if (meshes.has(room.roomId)) return; // idempotent\n const viewer = new MeetViewer({\n name: room.name ?? \"solar-viewer\",\n ...room,\n ...(options.deps !== undefined && room.deps === undefined ? { deps: options.deps } : {}),\n });\n wireViewer(viewer, registry, claim);\n meshes.set(room.roomId, { viewer });\n }\n\n function closeRoom(roomId: string): void {\n const mesh = meshes.get(roomId);\n if (mesh === undefined) return;\n // Release every label this viewer owns BEFORE closing, so a surviving room\n // can take over the label and the registry drops the gone stream.\n for (const [label, owner] of [...owners.entries()]) {\n if (owner === mesh.viewer) {\n registry.remove(label);\n owners.delete(label);\n }\n }\n mesh.viewer.leave();\n meshes.delete(roomId);\n }\n\n for (const room of options.rooms) openRoom(room);\n\n return {\n join: async () => {\n await Promise.all([...meshes.values()].map((m) => m.viewer.join()));\n },\n leave: () => {\n for (const roomId of [...meshes.keys()]) closeRoom(roomId);\n registry.clear();\n },\n setRooms: async (rooms) => {\n const next = new Set(rooms.map((r) => r.roomId));\n // Close rooms no longer present.\n for (const roomId of [...meshes.keys()]) {\n if (!next.has(roomId)) closeRoom(roomId);\n }\n // Open + join rooms newly added.\n const added: MeetViewer[] = [];\n for (const room of rooms) {\n if (!meshes.has(room.roomId)) {\n openRoom(room);\n const m = meshes.get(room.roomId);\n if (m) added.push(m.viewer);\n }\n }\n await Promise.all(added.map((v) => v.join()));\n },\n resolvePeerStream: (peerLabel) => registry.resolve(labelKey(peerLabel)),\n subscribePeerStream: (peerLabel, listener) => registry.subscribe(labelKey(peerLabel), listener),\n registry,\n };\n}\n\n/** The shape the Prism host injects on `window.__ZAB_PEER_VIEWER__`. The FINAL\n * model is the multi-room `{ rooms: [...] }` ; the legacy single-room shape is\n * still accepted for back-compat (treated as a one-room array). */\nexport type PeerViewerInjection =\n | MultiRoomPeerViewerOptions\n | (Omit<MeetViewerOptions, \"name\"> & { name?: string });\n\n/** Normalise either injection shape into a multi-room viewer. forge-prism passes\n * `window.__ZAB_PEER_VIEWER__` straight through ; a bare single-room object is\n * wrapped as `{ rooms: [it] }`. */\nexport function createPeerViewerFromInjection(injection: PeerViewerInjection): MultiRoomPeerViewer {\n if (\"rooms\" in injection && Array.isArray(injection.rooms)) {\n return createMultiRoomPeerViewer(injection);\n }\n // Legacy single-room shape → one-room array.\n const { name, deps, ...room } = injection as Omit<MeetViewerOptions, \"name\"> & {\n name?: string;\n };\n return createMultiRoomPeerViewer({\n rooms: [{ ...room, ...(name !== undefined ? { name } : {}) }],\n ...(deps !== undefined ? { deps } : {}),\n });\n}\n","// Public headless render entry — render an already-compiled `RenderBundle`\n// into a live DOM node, no WebSocket, ready when layout + fonts have settled\n// (ADR 003 §3.1). The host (Playwright / Chromium / a CEF offscreen surface)\n// screenshots `target` once `ready` resolves. The runtime does DOM + readiness\n// ONLY — no screenshot, no fetch (ADR 003 D5/D3).\n//\n// This is the zero-loss harness (ADR 002 #J) generalised: it mounts the EXACT\n// production seam —\n// LumencastRuntimeProvider{ mode:\"broadcast\", status:\"live\" } > BroadcastMode\n// — into a real `createRoot(target)`, NOT `renderToStaticMarkup` (which yields\n// unlaid-out markup: unmeasured fonts, uncomposited masks → an infidel PNG,\n// ADR 003 §3.1). `BroadcastMode` is dynamically imported so the headless\n// function adds no weight to the eager `mount`/broadcast path (ADR 003 §4,\n// RC6); the heavy render code already lives in the broadcast/tree chunks.\n//\n// Asset resolution is the HOST's job, done in the bundle BEFORE this call\n// (ADR 003 §3.2): the runtime renders the bundle as-is, gating every remaining\n// `src` through the unchanged deny-by-default host-allow gate inside\n// `BroadcastMode` (`AllowedHostsProvider`). A `src` on a host not in the\n// bundle's `allowedHosts` is omitted + a diagnostic is emitted — never faked\n// (ADR 002 borne, D4). Use `render/asset-resolve` helpers to pre-resolve.\n\nimport { StrictMode } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport { createStore } from \"../state/store.js\";\nimport { LumencastRuntimeProvider } from \"../overlay/runtime-context.js\";\nimport { addDiagnosticsHandler, type DiagnosticHandler } from \"./diagnostics.js\";\nimport type { RenderBundle } from \"./bundle.js\";\n\n/** Default stage size — the Figma 817:3 cover frame, the SSIM reference. */\nconst DEFAULT_STAGE = { width: 1920, height: 1080 } as const;\n\nexport interface HeadlessRenderOptions {\n /** Already-compiled bundle (via `@lumencast/compiler` on the host side). */\n bundle: RenderBundle;\n /** A live, mounted DOM node. Its size is set from `stage` unless the host\n * has already dimensioned it (see `stage`). */\n target: HTMLElement;\n /** Initial leaf-grain store state (`store.reset(defaults)`) — the bound\n * values the bundle reads (`__lit.*`, score, names…). */\n defaults?: Record<string, unknown>;\n /** Stage dimensions in CSS px. Defaults to 1920×1080. Applied to `target`\n * as `width`/`height`/`position:relative`/`overflow:hidden` so the\n * screenshot frame matches the reference exactly. */\n stage?: { width: number; height: number };\n /** Anti-drop diagnostics channel (ADR 001 §3.4): omitted assets, unhonoured\n * fields surface here as `{ nodeId, field, reason }` (never a value — R9).\n * Wired to the same global channel `mount()` uses. */\n onDiagnostic?: DiagnosticHandler;\n}\n\nexport interface HeadlessRenderHandle {\n /** Resolves after the scene has rendered, two animation frames have passed\n * AND `document.fonts.ready` (ADR 003 §3.3) — i.e. the DOM is laid out and\n * fonts are loaded, so a screenshot taken now is fidelity-faithful. */\n ready: Promise<void>;\n /** Tear down the React root and detach the diagnostics handler. */\n unmount(): void;\n}\n\nconst noop = (): void => {};\n\n/**\n * Render `bundle` into `target` through the production broadcast path and\n * resolve `ready` once it is settled. The runtime performs NO network fetch and\n * takes NO screenshot — it produces a settled live DOM and a readiness signal,\n * nothing more (ADR 003 D5).\n */\nexport function renderBundleHeadless(opts: HeadlessRenderOptions): HeadlessRenderHandle {\n const stage = opts.stage ?? DEFAULT_STAGE;\n const target = opts.target;\n // Pose the stage so the screenshot frame is exact (mirrors harness.html).\n target.style.position ||= \"relative\";\n target.style.width = `${stage.width}px`;\n target.style.height = `${stage.height}px`;\n target.style.overflow = \"hidden\";\n\n const removeDiagnostics = opts.onDiagnostic\n ? addDiagnosticsHandler(opts.onDiagnostic)\n : undefined;\n\n const store = createStore();\n store.reset(opts.defaults ?? {});\n\n const root = createRoot(target);\n\n const ready = new Promise<void>((resolve) => {\n // BroadcastMode is dynamically imported so its (and the tree's) weight is\n // not pulled into the eager `mount` entry chunk (RC6). It is already a\n // separate chunk reused from the broadcast path.\n void import(\"../modes/broadcast.js\").then(({ BroadcastMode }) => {\n root.render(\n <StrictMode>\n <LumencastRuntimeProvider\n value={{\n mode: \"broadcast\",\n store,\n bundle: opts.bundle,\n status: \"live\",\n sendInput: noop,\n }}\n >\n <BroadcastMode />\n </LumencastRuntimeProvider>\n </StrictMode>,\n );\n\n // Settle: two animation frames (layout) AND fonts loaded (ADR 003 §3.3).\n // Both must complete before `ready` resolves, so a screenshot taken on\n // `ready` uses the brand glyphs, not the fallback font (no FOUT freeze).\n const framesSettled = new Promise<void>((res) => {\n requestAnimationFrame(() => requestAnimationFrame(() => res()));\n });\n const fontsReady =\n typeof document !== \"undefined\" && document.fonts\n ? document.fonts.ready.then(() => undefined)\n : Promise.resolve();\n void Promise.all([framesSettled, fontsReady]).then(() => resolve());\n });\n });\n\n return {\n ready,\n unmount() {\n removeDiagnostics?.();\n root.unmount();\n },\n };\n}\n","// Public asset + font resolution helpers for headless / host render (ADR 003 §3.2).\n//\n// These utilities let a HOST (Solar headless entry, a ZabCanvas render worker,\n// the zero-loss harness) resolve a bundle's content-addressed asset references\n// to concrete `data:` URIs and inject brand `@font-face`s BEFORE the first\n// frame — exactly as the zero-loss harness has always done (ADR 002 #J). They\n// are promoted here verbatim so every host resolves identically and exercises\n// the SAME host-allow gate the runtime applies internally.\n//\n// ── No-fetch contract (ADR 003 §3.2, D3, Bastion R2) ────────────────────────\n// NONE of these helpers performs a network fetch. They only rewrite a bundle's\n// `src` values against a caller-supplied table (`resolveSrc` /\n// `rewriteLayoutSrcs` / `rewriteDefaultsSrcs`) and load `@font-face`s from\n// caller-supplied `data:`/same-document URLs (`injectFonts`). The runtime never\n// reaches the network; the host owns where bytes come from. Both the input\n// table values and the font `src`s are expected to be `data:` (or otherwise\n// already host-allowed) URIs — substituting one already-admitted scheme for\n// another, so the deny-by-default `allowedHosts` gate stays the sole authority.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** A table mapping a bundle `src` reference to its resolved `data:` URI.\n * Keys may be either the full `assets/<hash>.ext` path or a bare `<hash>`. */\nexport type AssetTable = Readonly<Record<string, string>>;\n\n/** Resolve a single `src` value against a table. Non-string or unmatched values\n * pass through unchanged. Matches both `assets/<hash>.ext` and bare `<hash>`. */\nexport function resolveSrc(src: unknown, table: AssetTable): unknown {\n if (typeof src !== \"string\") return src;\n if (table[src]) return table[src];\n const m = /^assets\\/([A-Za-z0-9]+)\\.[A-Za-z0-9]+$/.exec(src);\n if (m && m[1] !== undefined && table[m[1]]) return table[m[1]];\n return src;\n}\n\n/** Deep-rewrite every `src` (image-fill, mask source) in a layout subtree,\n * in place, against the supplied asset table. */\nexport function rewriteLayoutSrcs(node: unknown, table: AssetTable): void {\n if (node === null || typeof node !== \"object\") return;\n if (Array.isArray(node)) {\n for (const n of node) rewriteLayoutSrcs(n, table);\n return;\n }\n const obj = node as Record<string, unknown>;\n if (\"src\" in obj) obj[\"src\"] = resolveSrc(obj[\"src\"], table);\n for (const v of Object.values(obj)) {\n if (v && typeof v === \"object\") rewriteLayoutSrcs(v, table);\n }\n}\n\n/** Rewrite the `__lit.image.*` defaults (image-primitive `bind.src` targets)\n * against the asset table, returning a new defaults object. */\nexport function rewriteDefaultsSrcs(\n defaults: Record<string, unknown>,\n table: AssetTable,\n): Record<string, unknown> {\n const out: Record<string, unknown> = { ...defaults };\n for (const [k, v] of Object.entries(out)) {\n if (k.startsWith(\"__lit.image.\")) out[k] = resolveSrc(v, table);\n }\n return out;\n}\n\n/** A brand `@font-face` to inject before render. `src` is a `data:` URI (or any\n * same-document URL) so no network/host-allow surface is involved. */\nexport interface FontFaceSpec {\n family: string;\n weight: number | string;\n style?: string;\n /** `url(data:font/woff2;base64,…)` content (the value inside `src:`). */\n src: string;\n}\n\n/** Inject `@font-face` rules and block until the faces are loaded, so the very\n * first painted frame already uses the brand glyphs (no fallback-font flash).\n * Returns the families that successfully loaded. Loads only from the supplied\n * `src` (expected `data:`); never fetches a remote host on its own behalf. */\nexport async function injectFonts(faces: readonly FontFaceSpec[]): Promise<string[]> {\n const loaded: string[] = [];\n for (const f of faces) {\n try {\n const face = new FontFace(f.family, f.src, {\n weight: String(f.weight),\n style: f.style ?? \"normal\",\n });\n await face.load();\n (document as Document & { fonts: FontFaceSet }).fonts.add(face);\n loaded.push(f.family);\n } catch {\n // A font that fails to load is a documented gap, not a render-breaker:\n // the fallback glyphs paint and the host can detect the missing family\n // in the returned list. Never throw (would abort an otherwise-good\n // render) and never log the value (R9) — the absent family name is in\n // `f.family`, which the caller already holds.\n }\n }\n return loaded;\n}\n"],"names":["Ctx","createContext","LumencastRuntimeProvider","value","children","jsx","useLumencastRuntime","v","useContext","useOptionalLumencastRuntime","LazyBroadcastMode","lazy","m","LazyControlMode","LazyTestMode","LumencastApp","mode","store","bundleSignal","statusSignal","crossfadeKeySignal","sendInput","resolveCaptureDevice","resolvePeerStream","subscribePeerStream","useSignals","bundle","status","trackKey","ModeComponent","AnimatePresence","motion","Suspense","ANON_NODE_ID","handlers","addDiagnosticsHandler","handler","emitDiagnostic","nodeId","field","reason","diagnostic","MAX_FILTER_BLUR_PX","MAX_FILTER_BRIGHTNESS","MAX_FILTER_STRING_LEN","CAPS","clampFilterChannel","channel","cap","FILTER_STRING_RE","FILTER_IDENTITY","sanitizeCssFilterString","blur","brightness","warnRejectedFilter","NO_ANIMATION","EASE_MAP","toFramer","INITIAL_IDENTITY","DEFAULT_MOUNT_PLAY_TRANSITION","resolveTransition","transitionFor","keys","animateInitial","key","t","mountPlay","base","initial","from","safe","animate","parseWireTransition","kind","duration_ms","easing","WIRE_EASING_MAP","out","applyDelta","frame","batch","patch","transition","applySnapshot","CAM_RESERVED_PREFIX","CAM_SLOTS_PREFIX","CAM_VIEWER_LEAF","isReservedCamPath","path","project","raw","slots","viewer","slotRef","identity","leaves","k","createReservedLeafObserver","emit","last","flush","state","patches","touched","StoreImpl","signal","s","shallowEqual","ts","seen","createStore","a","b","i","ao","bo","ak","bk","SUPPORTED_PROFILES","AUTHORING_NAME_RE","AUTHORING_MAJOR_RE","AUTHORING_SUFFIX","isAuthoringProfile","id","slash","name","major","BundleIncompatibleError","unsupportedProfiles","validateBundleProfiles","supported","profiles","missing","p","FetcherImpl","opts","token","sceneId","sceneVersion","cached","existing","promise","url","init","response","json","createBundleFetcher","DEFAULTS","ScheduleImpl","random","attempt","offset","createReconnectSchedule","merged","TransportError","message","recoverable","code","cause","WsClient","SequenceTracker","ctor","resolveToken","encodeFrame","inputFrame","resolvedToken","err","socket","WS_SUBPROTOCOLS","event","canResume","WS_SUBPROTOCOL_V1_1","sinceSequence","subscribeFrame","data","decodeServerFrame","LumencastError","obs","_event","immediate","delay","next","validateOptions","options","mount","baseUrl","deriveBaseUrl","bundleFetcher","ws","setStatus","reportError","active","warmedVersions","reservedLeaves","removeDiagnosticsHandler","onSnapshot","start","warmRoster","transportToLumencastError","root","createRoot","createElement","entries","source","activeVersion","scene_id","scene_version","fetcher","bSignal","cSignal","applyState","onErr","wsUrl","u","UNIVERSAL_PROPS","allow","PRIMITIVE_PROP_ALLOWLIST","isAllowed","allowed","checkedNodes","checkNodeProps","node","MeetViewer","type","listener","set","resolve","reject","onOpen","onError","ev","msg","r","peer","remote","payload","pc","desc","offerCollision","c","stream","audioTx","videoTx","pinViewerCodecs","candidate","track","otherId","to","getCaps","pinKind","mime","tx","caps","keep","setPrefs","codecs","createPeerStreamRegistry","streams","listeners","rosterListeners","notify","peerLabel","notifyRoster","isNew","labels","label","labelKey","wireViewer","registry","claim","e","SOLE_OWNER","createPeerViewer","createMultiRoomPeerViewer","meshes","owners","owner","openRoom","room","closeRoom","roomId","mesh","rooms","added","createPeerViewerFromInjection","injection","deps","DEFAULT_STAGE","noop","renderBundleHeadless","stage","target","removeDiagnostics","BroadcastMode","StrictMode","framesSettled","res","fontsReady","resolveSrc","src","table","rewriteLayoutSrcs","obj","rewriteDefaultsSrcs","defaults","injectFonts","faces","loaded","f","face"],"mappings":"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"}
package/dist/index.html CHANGED
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <meta name="generator" content="@lumencast/runtime 0.12.0" />
6
+ <meta name="generator" content="@lumencast/runtime 0.12.2" />
7
7
  <title>Lumencast</title>
8
8
  <style>
9
9
  html, body { margin: 0; padding: 0; height: 100%; background: #000; }
package/dist/lumencast.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as s, B as r, C as t, d as i, f as o, M as P, P as n, S as l, g as E, h as R, i as c, j as d, k as I, l as S, n as _, o as u, p as A, q as m, v as L, x as M, y as O, z as V } from "./index-ClWi5UzJ.js";
1
+ import { A as s, B as r, C as t, d as i, f as o, M as P, P as n, S as l, g as E, h as R, i as c, j as d, k as I, l as S, n as _, o as u, p as A, q as m, v as L, x as M, y as O, z as V } from "./index-BH-3p9mt.js";
2
2
  export {
3
3
  s as ANON_NODE_ID,
4
4
  r as BundleIncompatibleError,
@@ -1 +1 @@
1
- {"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../../src/render/bundle.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../../src/render/bundle.ts"],"names":[],"mappings":"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"}
@@ -110,6 +110,7 @@ export function validateBundleProfiles(bundle, supported = SUPPORTED_PROFILES) {
110
110
  }
111
111
  class FetcherImpl {
112
112
  cache = new Map();
113
+ inFlight = new Map();
113
114
  baseUrl;
114
115
  pathPrefix;
115
116
  resolveUrl;
@@ -150,6 +151,19 @@ class FetcherImpl {
150
151
  const cached = this.cache.get(sceneVersion);
151
152
  if (cached)
152
153
  return cached;
154
+ // Bundles are content-addressed by scene_version, so a concurrent miss for
155
+ // the same version can share a single fetch. The entry is cleared in
156
+ // `finally` — a rejected fetch is never cached, so a later call retries.
157
+ const existing = this.inFlight.get(sceneVersion);
158
+ if (existing)
159
+ return existing;
160
+ const promise = this.fetchBundle(sceneId, sceneVersion).finally(() => {
161
+ this.inFlight.delete(sceneVersion);
162
+ });
163
+ this.inFlight.set(sceneVersion, promise);
164
+ return promise;
165
+ }
166
+ async fetchBundle(sceneId, sceneVersion) {
153
167
  const url = this.buildUrl(sceneId, sceneVersion);
154
168
  const init = await this.buildInit();
155
169
  const response = init ? await this.fetchImpl(url, init) : await this.fetchImpl(url);
@@ -1 +1 @@
1
- {"version":3,"file":"bundle.js","sourceRoot":"","sources":["../../src/render/bundle.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bundle.js","sourceRoot":"","sources":["../../src/render/bundle.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,EAAE;AACF,oEAAoE;AACpE,0EAA0E;AAC1E,8EAA8E;AAC9E,EAAE;AACF,6EAA6E;AAC7E,iFAAiF;AACjF,8EAA8E;AAC9E,0EAA0E;AAC1E,yEAAyE;AACzE,mDAAmD;AAmInD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAwB,IAAI,GAAG,CAAS;IACrE,4BAA4B;IAC5B,uEAAuE;IACvE,0EAA0E;IAC1E,iDAAiD;IACjD,iBAAiB;CAClB,CAAC,CAAC;AAEH,kEAAkE;AAClE,EAAE;AACF,wEAAwE;AACxE,yEAAyE;AACzE,0EAA0E;AAC1E,mCAAmC;AACnC,EAAE;AACF,yEAAyE;AACzE,0EAA0E;AAC1E,kDAAkD;AAClD,uEAAuE;AACvE,yEAAyE;AACzE,kDAAkD;AAClD,EAAE;AACF,sEAAsE;AACtE,wEAAwE;AACxE,uEAAuE;AACvE,MAAM,iBAAiB,GAAG,iCAAiC,CAAC;AAC5D,MAAM,kBAAkB,GAAG,qBAAqB,CAAC;AACjD,MAAM,gBAAgB,GAAG,YAAY,CAAC;AAEtC;;0EAE0E;AAC1E,MAAM,UAAU,kBAAkB,CAAC,EAAU;IAC3C,MAAM,KAAK,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5B,MAAM,IAAI,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAChC,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IAClC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAClD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QAAE,OAAO,KAAK,CAAC;IACnD,uEAAuE;IACvE,uEAAuE;IACvE,iEAAiE;IACjE,sEAAsE;IACtE,OAAO,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAChC,IAAI,GAAG,qBAA8B,CAAC;IACtC,mBAAmB,CAAW;IAC9C,YAAY,mBAA6B;QACvC,KAAK,CACH,kEAAkE,mBAAmB,CAAC,IAAI,CACxF,IAAI,CACL,EAAE,CACJ,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;QACtC,IAAI,CAAC,mBAAmB,GAAG,mBAAmB,CAAC;IACjD,CAAC;CACF;AAED;;;;;;;;;;;;;;;wBAewB;AACxB,MAAM,UAAU,sBAAsB,CACpC,MAA+B,EAC/B,YAAiC,kBAAkB;IAEnD,MAAM,QAAQ,GAAY,MAAM,CAAC,QAAQ,CAAC;IAC1C,IAAI,CAAC,QAAQ;QAAE,OAAO;IACtB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,uBAAuB,CAAC,CAAC,uCAAuC,CAAC,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAClC,MAAM,OAAO,GAAG,QAAQ;SACrB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;SACrF,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,uCAAuC,CAAC,CAAC,CAAC;IACrF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,uBAAuB,CAAC,OAAO,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC;AA0CD,MAAM,WAAW;IACE,KAAK,GAAG,IAAI,GAAG,EAAwB,CAAC;IACxC,QAAQ,GAAG,IAAI,GAAG,EAAiC,CAAC;IACpD,OAAO,CAAS;IAChB,UAAU,CAAS;IACnB,UAAU,CAAgC;IAC1C,YAAY,CAAuC;IACnD,SAAS,CAAe;IAEzC,YAAY,IAA0B;QACpC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC/C,IAAI,CAAC,UAAU,GAAG,CAAC,IAAI,CAAC,UAAU,IAAI,iBAAiB,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5E,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QAClC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;QACtC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACvE,CAAC;IAED;;uCAEmC;IAC3B,KAAK,CAAC,SAAS;QACrB,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE,OAAO,SAAS,CAAC;QACzC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;QACxC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAC7B,OAAO,EAAE,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE,EAAE,CAAC;IAC3D,CAAC;IAEO,QAAQ,CAAC,OAAe,EAAE,YAAoB;QACpD,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QAChD,CAAC;QACD,OAAO,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,UAAU,IAAI,kBAAkB,CAAC,OAAO,CAAC,aAAa,kBAAkB,CAAC,YAAY,CAAC,EAAE,CAAC;IACzH,CAAC;IAED,OAAO,CAAC,MAAoB;QAC1B,6DAA6D;QAC7D,+DAA+D;QAC/D,wCAAwC;QACxC,sBAAsB,CAAC,MAAM,CAAC,CAAC;QAC/B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,OAAe,EAAE,YAAoB;QAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAC5C,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QAC1B,2EAA2E;QAC3E,qEAAqE;QACrE,yEAAyE;QACzE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACjD,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;YACnE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACzC,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,KAAK,CAAC,WAAW,CAAC,OAAe,EAAE,YAAoB;QAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QACjD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACpF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,wBAAwB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QACpF,CAAC;QACD,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAiB,CAAC;QACrD,IAAI,IAAI,CAAC,aAAa,KAAK,YAAY,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CACb,2CAA2C,YAAY,SAAS,IAAI,CAAC,aAAa,EAAE,CACrF,CAAC;QACJ,CAAC;QACD,sBAAsB,CAAC,IAAI,CAAC,CAAC;QAC7B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED,MAAM,UAAU,mBAAmB,CAAC,IAA0B;IAC5D,OAAO,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { ResolveCaptureDevice } from "./capture";
2
+ /** Outcome of a claim: either a shared stream promise (with the key to release
3
+ * later) or PLACEHOLDER — the unknown-kind / declared-but-unresolved-ref path
4
+ * that acquires nothing, exactly as the old inline `acquireStream` returning
5
+ * `null` did. A PLACEHOLDER claim holds NO ref (nothing to release). */
6
+ export type CaptureClaim = {
7
+ kind: "stream";
8
+ key: string;
9
+ promise: Promise<MediaStream>;
10
+ } | {
11
+ kind: "placeholder";
12
+ };
13
+ /** Resolve the device, then claim a shared stream for it (incrementing the
14
+ * ref-count), or return PLACEHOLDER. Awaits the resolver first — physical ids
15
+ * are salted per origin/partition, so the host may re-resolve a portable key
16
+ * (label) against THIS context (capture.tsx §A1.3). A throw from the underlying
17
+ * `getUserMedia` surfaces via the returned promise (caller → PLACEHOLDER). */
18
+ export declare function claimCaptureStream(sourceKind: string, deviceRef: string, resolveCaptureDevice: ResolveCaptureDevice | undefined): Promise<CaptureClaim>;
19
+ /** Drop one consumer's claim. Stops every track (RC11 — kill the device light)
20
+ * ONLY when the last consumer releases. A no-op for an already-evicted key
21
+ * (e.g. an acquisition that rejected and self-evicted). */
22
+ export declare function releaseCaptureStream(key: string): void;
23
+ /** Test-only: drop all cached entries WITHOUT stopping tracks. Lets a test file
24
+ * start from a clean ref-count without leaking state between cases. */
25
+ export declare function __resetCaptureStreamCache(): void;
26
+ //# sourceMappingURL=capture-stream-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture-stream-cache.d.ts","sourceRoot":"","sources":["../../../src/render/primitives/capture-stream-cache.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAUtD;;;yEAGyE;AACzE,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;CAAE,GAC9D;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,CAAC;AAE5B;;;;+EAI+E;AAC/E,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,oBAAoB,EAAE,oBAAoB,GAAG,SAAS,GACrD,OAAO,CAAC,YAAY,CAAC,CA0EvB;AAED;;4DAE4D;AAC5D,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAOtD;AAqBD;wEACwE;AACxE,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD"}