@camstack/ui-library 1.2.241 → 1.2.243

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.
package/dist/index.js CHANGED
@@ -8002,16 +8002,20 @@ var usePipelineOrchestratorSetAgentInferenceDevices = trpc.pipelineOrchestrator.
8002
8002
  var usePipelineOrchestratorGetNodeInferenceDevices = trpc.pipelineOrchestrator.getNodeInferenceDevices.useQuery;
8003
8003
  /** Generated alias around `trpc.pipelineOrchestrator.resetNodePipelineDefaults.useMutation`. */
8004
8004
  var usePipelineOrchestratorResetNodePipelineDefaults = trpc.pipelineOrchestrator.resetNodePipelineDefaults.useMutation;
8005
- /** Generated alias around `trpc.pipelineOrchestrator.planBestSettings.useQuery`. */
8006
- var usePipelineOrchestratorPlanBestSettings = trpc.pipelineOrchestrator.planBestSettings.useQuery;
8007
- /** Generated alias around `trpc.pipelineOrchestrator.applyBestSettings.useMutation`. */
8008
- var usePipelineOrchestratorApplyBestSettings = trpc.pipelineOrchestrator.applyBestSettings.useMutation;
8005
+ /** Generated alias around `trpc.pipelineOrchestrator.planAutoTune.useQuery`. */
8006
+ var usePipelineOrchestratorPlanAutoTune = trpc.pipelineOrchestrator.planAutoTune.useQuery;
8007
+ /** Generated alias around `trpc.pipelineOrchestrator.applyAutoTune.useMutation`. */
8008
+ var usePipelineOrchestratorApplyAutoTune = trpc.pipelineOrchestrator.applyAutoTune.useMutation;
8009
8009
  /** Generated alias around `trpc.pipelineOrchestrator.setLoadBalancingShares.useMutation`. */
8010
8010
  var usePipelineOrchestratorSetLoadBalancingShares = trpc.pipelineOrchestrator.setLoadBalancingShares.useMutation;
8011
8011
  /** Generated alias around `trpc.pipelineOrchestrator.getPlacementStats.useQuery`. */
8012
8012
  var usePipelineOrchestratorGetPlacementStats = trpc.pipelineOrchestrator.getPlacementStats.useQuery;
8013
8013
  /** Generated alias around `trpc.pipelineOrchestrator.suggestShares.useQuery`. */
8014
8014
  var usePipelineOrchestratorSuggestShares = trpc.pipelineOrchestrator.suggestShares.useQuery;
8015
+ /** Generated alias around `trpc.pipelineOrchestrator.getEffectiveShares.useQuery`. */
8016
+ var usePipelineOrchestratorGetEffectiveShares = trpc.pipelineOrchestrator.getEffectiveShares.useQuery;
8017
+ /** Generated alias around `trpc.pipelineOrchestrator.setShareSource.useMutation`. */
8018
+ var usePipelineOrchestratorSetShareSource = trpc.pipelineOrchestrator.setShareSource.useMutation;
8015
8019
  /** Generated alias around `trpc.pipelineOrchestrator.getCameraSettings.useQuery`. */
8016
8020
  var usePipelineOrchestratorGetCameraSettings = trpc.pipelineOrchestrator.getCameraSettings.useQuery;
8017
8021
  /** Generated alias around `trpc.pipelineOrchestrator.setCameraStepToggle.useMutation`. */
@@ -31607,6 +31611,27 @@ function nextClientSample(prev, cur, ctx) {
31607
31611
  };
31608
31612
  }
31609
31613
  //#endregion
31614
+ //#region src/composites/peer-connection-config.ts
31615
+ /**
31616
+ * The player's `RTCConfiguration`, in one place for every connect path.
31617
+ *
31618
+ * The player never forces `iceTransportPolicy: 'relay'` on its own (werift's
31619
+ * relay forward is unreliable across two real TURN servers, and relay would
31620
+ * defeat the direct LAN/Tailscale path). The policy exists for the WebRTC
31621
+ * diagnostics only, which run the SAME player once with `all` and once with
31622
+ * `relay` to tell a reachable relay path from a dead one. Absent, the key is
31623
+ * not set at all, so a production connect is byte-for-byte what it was.
31624
+ */
31625
+ /** Public STUN kept as a fallback beside whatever the hub's TURN provider returns. */
31626
+ var FALLBACK_STUN_SERVER = { urls: "stun:stun.l.google.com:19302" };
31627
+ function buildPeerConnectionConfig(iceServers, iceTransportPolicy) {
31628
+ const servers = [...iceServers ?? [], FALLBACK_STUN_SERVER];
31629
+ return iceTransportPolicy === void 0 ? { iceServers: servers } : {
31630
+ iceServers: servers,
31631
+ iceTransportPolicy
31632
+ };
31633
+ }
31634
+ //#endregion
31610
31635
  //#region src/composites/stream-debug/webrtc-debug-stats.ts
31611
31636
  /** Beyond this the log is a scroll, not a signal. */
31612
31637
  var RESOLUTION_LOG_MAX = 8;
@@ -32330,7 +32355,7 @@ function mergePlayerHints(autoHints, override, downlinkMbps, coldStart) {
32330
32355
  return coldStart ? stripDownlinkForColdStart(merged) : merged;
32331
32356
  }
32332
32357
  var FIRST_FRAME_TIMEOUT_MS = 8e3;
32333
- function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, muted: initialMuted = true, showControls = true, showStats = false, onDebugStats, onPlaybackStats, onClientNetworkSample, probeAuthToken, onConnectTiming, className = "", onStateChange, onError, onReconnectAttempt, overlay, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, getSessionDebug, applyDownlinkProbe, reoffer, posterUrl, hintsOverride, onAdaptiveHints, reconnectSignal, onControlChannel, onVideoElement }) {
32358
+ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, muted: initialMuted = true, showControls = true, showStats = false, onDebugStats, onPlaybackStats, onClientNetworkSample, probeAuthToken, onConnectTiming, className = "", onStateChange, onError, onReconnectAttempt, overlay, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, getSessionDebug, applyDownlinkProbe, reoffer, posterUrl, hintsOverride, onAdaptiveHints, reconnectSignal, onControlChannel, onVideoElement, iceTransportPolicy, onPeerConnection }) {
32334
32359
  const videoRef = useRef(null);
32335
32360
  const containerRef = useRef(null);
32336
32361
  const pcRef = useRef(null);
@@ -32346,6 +32371,12 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
32346
32371
  * consumer, without re-creating the connect callback on every render. */
32347
32372
  const onControlChannelRef = useRef(onControlChannel);
32348
32373
  onControlChannelRef.current = onControlChannel;
32374
+ /** Read at connect time through refs so neither diagnostics prop re-creates
32375
+ * the connect callbacks (and so neither forces a reconnect when it changes). */
32376
+ const iceTransportPolicyRef = useRef(iceTransportPolicy);
32377
+ iceTransportPolicyRef.current = iceTransportPolicy;
32378
+ const onPeerConnectionRef = useRef(onPeerConnection);
32379
+ onPeerConnectionRef.current = onPeerConnection;
32349
32380
  /** Same pattern for the video-element handle: delivered once on mount, null
32350
32381
  * on unmount — the ref keeps the latest consumer without re-running. */
32351
32382
  const onVideoElementRef = useRef(onVideoElement);
@@ -32555,7 +32586,8 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
32555
32586
  sessionIdsRef.current.delete(sessionId);
32556
32587
  return;
32557
32588
  }
32558
- const pc = new RTCPeerConnection({ iceServers: [...iceServers ?? [], { urls: "stun:stun.l.google.com:19302" }] });
32589
+ const pc = new RTCPeerConnection(buildPeerConnectionConfig(iceServers, iceTransportPolicyRef.current));
32590
+ onPeerConnectionRef.current?.(pc);
32559
32591
  if (!seamless) pcRef.current = pc;
32560
32592
  const combinedStream = new MediaStream();
32561
32593
  if (!withDownlink) probeAfterThisStreamsFirstFrame({
@@ -32756,7 +32788,8 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
32756
32788
  const iceServers = await getIceServers?.();
32757
32789
  timing.iceServersMs = ms();
32758
32790
  if (!mountedRef.current || abort.signal.aborted) return;
32759
- const pc = new RTCPeerConnection({ iceServers: [...iceServers ?? [], { urls: "stun:stun.l.google.com:19302" }] });
32791
+ const pc = new RTCPeerConnection(buildPeerConnectionConfig(iceServers, iceTransportPolicyRef.current));
32792
+ onPeerConnectionRef.current?.(pc);
32760
32793
  if (!seamless) pcRef.current = pc;
32761
32794
  const combinedStream = new MediaStream();
32762
32795
  /** This connect's server session — known once `handleOffer` answers,
@@ -32962,7 +32995,8 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
32962
32995
  try {
32963
32996
  const iceServers = await getIceServers?.();
32964
32997
  if (!mountedRef.current) return;
32965
- const pc = new RTCPeerConnection({ iceServers: [...iceServers ?? [], { urls: "stun:stun.l.google.com:19302" }] });
32998
+ const pc = new RTCPeerConnection(buildPeerConnectionConfig(iceServers, iceTransportPolicyRef.current));
32999
+ onPeerConnectionRef.current?.(pc);
32966
33000
  const newStream = new MediaStream();
32967
33001
  let swappedIn = false;
32968
33002
  let warmupStarted = false;
@@ -33802,281 +33836,6 @@ async function waitForIceGathering(pc) {
33802
33836
  });
33803
33837
  }
33804
33838
  //#endregion
33805
- //#region src/composites/cap-settings/scene-monitor-copy.ts
33806
- /** The lighting variants an operator is OFFERED. The resolver can also report
33807
- * `night`, `dawn` and `dusk`; those are shown when present but never demanded,
33808
- * because most installs only ever see day and IR. */
33809
- var SCENE_CAPTURE_VARIANTS = ["day", "ir"];
33810
- var VARIANT_LABEL = {
33811
- day: "Daylight",
33812
- ir: "Night (IR)",
33813
- night: "Night",
33814
- dawn: "Dawn",
33815
- dusk: "Dusk"
33816
- };
33817
- function variantLabel(condition) {
33818
- return VARIANT_LABEL[condition] ?? condition;
33819
- }
33820
- /**
33821
- * "Daylight ✓ · Night (IR) ✗ — optional; checks in night (ir) are skipped."
33822
- *
33823
- * Converts the silent cross-condition fallback this engine used to have into an
33824
- * operator-visible fact. Without it, a scene with only a daylight reference
33825
- * simply stops answering after sunset and nothing anywhere says why.
33826
- *
33827
- * It used to end *"reports 'can't tell' in night (ir)"*, which was accurate and
33828
- * read as a defect: the operator saw a ✗ and a complaint for a scene that was
33829
- * configured exactly as they meant it. The missing half is now named as what it
33830
- * is — the part you do not have to do — and the sentence says what actually
33831
- * happens instead, which differs by the scene's own policy.
33832
- */
33833
- function coverageLine(monitor) {
33834
- const covered = new Set(monitor.coveredConditions);
33835
- const parts = SCENE_CAPTURE_VARIANTS.map((v) => `${variantLabel(v)} ${covered.has(v) ? "✓" : "✗"}`);
33836
- const extra = [...covered].filter((c) => !SCENE_CAPTURE_VARIANTS.some((v) => v === c));
33837
- for (const c of extra) parts.push(`${variantLabel(c)} ✓`);
33838
- const missing = SCENE_CAPTURE_VARIANTS.filter((v) => !covered.has(v));
33839
- if (missing.length === 0) return parts.join(" · ");
33840
- if (covered.size === 0) return `${parts.join(" · ")} — nothing captured yet, so this scene is not watching.`;
33841
- const names = missing.map((v) => variantLabel(v).toLowerCase()).join(" and ");
33842
- return monitor.onUncoveredCondition === "judge-anyway" ? `${parts.join(" · ")} — ${names} judged against the references you do have.` : `${parts.join(" · ")} — optional; checks in ${names} are skipped.`;
33843
- }
33844
- /**
33845
- * The calm sentence for a scene that is sitting the current light out — `null`
33846
- * whenever it is checking normally.
33847
- *
33848
- * Separate from {@link statusLine} because it is not a verdict and must not
33849
- * read like one. A paused scene has not failed, has not lost its flag, and
33850
- * needs nothing from the operator; the only thing worth saying is which light
33851
- * it is waiting out and that it will pick itself back up.
33852
- */
33853
- function suspensionLine(monitor) {
33854
- if (monitor.suspendedCondition === null) return null;
33855
- const light = variantLabel(monitor.suspendedCondition).toLowerCase();
33856
- return `Paused — nothing was captured in ${light}, so checks are on hold until the light changes back. Capture ${light} if you want it watched around the clock.`;
33857
- }
33858
- var UNAVAILABLE_SENTENCE = {
33859
- "no-reference-for-condition": "can’t tell — nothing has been captured for this scene yet. Press “Capture daylight” to photograph what normal looks like.",
33860
- "view-shifted": "can’t tell — the camera view has moved since the reference was captured. Re-capture it.",
33861
- "no-vision-profile": "can’t tell — the vision model is unreachable, so the change is on hold.",
33862
- "encoder-model-changed": "can’t tell — the embedding model changed, so the stored references are unreadable. Re-capture them.",
33863
- "no-snapshot": "can’t tell — this camera did not return a frame."
33864
- };
33865
- /**
33866
- * The one line under a scene's name. It answers, in order: can it judge, what
33867
- * does it say, and how long has it said it.
33868
- */
33869
- function statusLine(monitor, now) {
33870
- if (monitor.states.length === 0) return "Not armed yet — capture the initial state to start watching.";
33871
- if (monitor.verdict === "unknown") return monitor.unavailable !== null ? UNAVAILABLE_SENTENCE[monitor.unavailable] : "can’t tell right now.";
33872
- const since = monitor.verdict === "diverged" ? monitor.divergedAt : monitor.armedAt;
33873
- const held = since !== null ? ` for ${humanDuration(Math.max(0, now - since))}` : "";
33874
- if (monitor.latched) return monitor.verdict === "diverged" ? `CHANGED${held} — press Reset when you have dealt with it.` : `Back to normal, but still flagged as changed${held}. Press Reset to clear it.`;
33875
- const paused = suspensionLine(monitor);
33876
- if (paused !== null) return paused;
33877
- return monitor.verdict === "diverged" ? `CHANGED${held}` : `Normal${held}`;
33878
- }
33879
- /** The badge word. `latched` wins over `verdict`, because the latch is the
33880
- * thing the operator asked to be told and it outlives the live state.
33881
- *
33882
- * A scene sitting out an uncovered light reads `Paused`, in the NEUTRAL tone —
33883
- * never `alarm`, and never the `danger` variant the page maps that to. A
33884
- * missing IR reference must not be able to paint a red badge on a scene that
33885
- * is doing exactly what it was configured to do. */
33886
- function badgeFor(monitor) {
33887
- if (monitor.states.length === 0) return {
33888
- text: "Not armed",
33889
- tone: "unknown"
33890
- };
33891
- if (monitor.latched) return {
33892
- text: "Changed",
33893
- tone: "alarm"
33894
- };
33895
- if (monitor.suspendedCondition !== null) return {
33896
- text: "Paused",
33897
- tone: "unknown"
33898
- };
33899
- if (monitor.verdict === "unknown") return {
33900
- text: "Can’t tell",
33901
- tone: "unknown"
33902
- };
33903
- return monitor.verdict === "diverged" ? {
33904
- text: "Changed",
33905
- tone: "alarm"
33906
- } : {
33907
- text: "Normal",
33908
- tone: "ok"
33909
- };
33910
- }
33911
- /** A rect covering most of the frame is dominated by weather, sky and parked
33912
- * cars: the bin is 2 % of the pixels and CLIP will not notice it leaving. The
33913
- * failure is silent INSENSITIVITY, which is the worst kind — hence a warning
33914
- * rather than a refusal. */
33915
- var ROI_AREA_WARN_FRACTION = .4;
33916
- function roiTooLarge(roi) {
33917
- return roi.width * roi.height > ROI_AREA_WARN_FRACTION;
33918
- }
33919
- function humanDuration(ms) {
33920
- const s = Math.floor(ms / 1e3);
33921
- if (s < 60) return `${s}s`;
33922
- const m = Math.floor(s / 60);
33923
- if (m < 60) return `${m}m`;
33924
- const h = Math.floor(m / 60);
33925
- if (h < 48) return `${h}h`;
33926
- return `${Math.floor(h / 24)}d`;
33927
- }
33928
- //#endregion
33929
- //#region src/composites/code-block.tsx
33930
- function CodeBlock({ children, maxHeight = 300, className }) {
33931
- const [copied, setCopied] = useState(false);
33932
- const handleCopy = useCallback(() => {
33933
- navigator.clipboard.writeText(children).then(() => {
33934
- setCopied(true);
33935
- setTimeout(() => setCopied(false), 2e3);
33936
- });
33937
- }, [children]);
33938
- return /* @__PURE__ */ jsxs("div", {
33939
- className: cn("relative group", className),
33940
- children: [/* @__PURE__ */ jsx(ScrollArea, {
33941
- style: { maxHeight },
33942
- children: /* @__PURE__ */ jsx("pre", {
33943
- className: "font-mono text-xs bg-surface p-3 rounded-md border border-border-subtle",
33944
- children: /* @__PURE__ */ jsx("code", { children })
33945
- })
33946
- }), /* @__PURE__ */ jsx("div", {
33947
- className: "absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity",
33948
- children: /* @__PURE__ */ jsx(IconButton, {
33949
- icon: copied ? Check : Copy,
33950
- "aria-label": "Copy code",
33951
- variant: "ghost",
33952
- size: "sm",
33953
- onClick: handleCopy
33954
- })
33955
- })]
33956
- });
33957
- }
33958
- //#endregion
33959
- //#region src/composites/confirm-action-button.tsx
33960
- /**
33961
- * ConfirmActionButton — destructive-action button with a modal confirm.
33962
- *
33963
- * Two-step UX for any operation operators shouldn't trigger by accident:
33964
- * reboot device, restart addon, regenerate token, etc. The trigger is a
33965
- * normal Button that opens a Dialog; confirming runs the async action,
33966
- * the trigger spinner-disables itself for the duration, and the dialog
33967
- * closes on success. Errors surface inline at the dialog's bottom.
33968
- *
33969
- * Generic on `TResult` so callers don't have to discard the return
33970
- * value. Pass an `icon` to render it inside the trigger (e.g. RotateCw
33971
- * for reboot, RefreshCw for restart).
33972
- */
33973
- function ConfirmActionButton({ label, icon: Icon, title = "Confirm action", description, confirmLabel, triggerVariant = "outline", confirmVariant = "danger", size = "sm", disabled, className, action, onSuccess }) {
33974
- const [open, setOpen] = useState(false);
33975
- const [running, setRunning] = useState(false);
33976
- const [error, setError] = useState(null);
33977
- const handleConfirm = async () => {
33978
- setError(null);
33979
- setRunning(true);
33980
- try {
33981
- const result = await action();
33982
- onSuccess?.(result);
33983
- setOpen(false);
33984
- } catch (err) {
33985
- setError(err instanceof Error ? err.message : String(err));
33986
- } finally {
33987
- setRunning(false);
33988
- }
33989
- };
33990
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Button, {
33991
- type: "button",
33992
- variant: triggerVariant,
33993
- size,
33994
- disabled,
33995
- onClick: () => {
33996
- setError(null);
33997
- setOpen(true);
33998
- },
33999
- className,
34000
- children: [Icon && /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" }), label]
34001
- }), /* @__PURE__ */ jsx(Dialog, {
34002
- open,
34003
- onOpenChange: (next) => !running && setOpen(next),
34004
- children: /* @__PURE__ */ jsxs(DialogContent, { children: [
34005
- /* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsxs(DialogTitle, {
34006
- className: "flex items-center gap-2",
34007
- children: [/* @__PURE__ */ jsx(TriangleAlert, { className: "h-4 w-4 text-amber-400" }), title]
34008
- }), typeof description === "string" ? /* @__PURE__ */ jsx(DialogDescription, { children: description }) : description] }),
34009
- error && /* @__PURE__ */ jsx("div", {
34010
- className: cn("mt-2 px-3 py-2 rounded border border-danger/40 bg-danger/10", "text-xs text-danger"),
34011
- children: error
34012
- }),
34013
- /* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button, {
34014
- type: "button",
34015
- variant: "ghost",
34016
- size,
34017
- disabled: running,
34018
- onClick: () => setOpen(false),
34019
- children: "Cancel"
34020
- }), /* @__PURE__ */ jsxs(Button, {
34021
- type: "button",
34022
- variant: confirmVariant,
34023
- size,
34024
- disabled: running,
34025
- onClick: () => {
34026
- handleConfirm();
34027
- },
34028
- children: [running && /* @__PURE__ */ jsx(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }), confirmLabel ?? label]
34029
- })] })
34030
- ] })
34031
- })] });
34032
- }
34033
- //#endregion
34034
- //#region src/composites/copy-button.tsx
34035
- /**
34036
- * `CopyButton` — a compact button that copies a string `value` to the
34037
- * clipboard via `navigator.clipboard.writeText`, showing a brief
34038
- * "copied" check state. Addon-agnostic; used wherever a secret / URL
34039
- * needs a one-click copy affordance (export setup panels, etc.).
34040
- */
34041
- var COPIED_RESET_MS = 2e3;
34042
- function CopyButton({ value, label, srLabel, className, disabled }) {
34043
- const [copied, setCopied] = useState(false);
34044
- const handleCopy = useCallback(() => {
34045
- if (!value) return;
34046
- navigator.clipboard.writeText(value).then(() => {
34047
- setCopied(true);
34048
- setTimeout(() => setCopied(false), COPIED_RESET_MS);
34049
- });
34050
- }, [value]);
34051
- return /* @__PURE__ */ jsxs(Button, {
34052
- size: "sm",
34053
- variant: "ghost",
34054
- type: "button",
34055
- disabled: disabled || value.length === 0,
34056
- onClick: handleCopy,
34057
- className: cn(className),
34058
- "aria-label": copied ? "Copied" : `Copy ${srLabel ?? label ?? "value"}`,
34059
- children: [copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 text-success" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" }), label ? /* @__PURE__ */ jsx("span", {
34060
- className: "ml-1",
34061
- children: copied ? "Copied" : label
34062
- }) : null]
34063
- });
34064
- }
34065
- //#endregion
34066
- //#region src/composites/data-table-layout.ts
34067
- /**
34068
- * At this many columns and above, `auto` switches a narrow viewport to cards.
34069
- * Three columns still fit a phone at a readable size; four do not.
34070
- */
34071
- var CARD_MODE_MIN_COLUMNS = 4;
34072
- function resolveTableLayout({ mode, columnCount, isNarrow }) {
34073
- if (!isNarrow) return "table";
34074
- if (mode === "scroll") return "table";
34075
- if (mode === "cards") return "cards";
34076
- if (columnCount === 0) return "table";
34077
- return columnCount >= 4 ? "cards" : "table";
34078
- }
34079
- //#endregion
34080
33839
  //#region src/lib/responsive.ts
34081
33840
  /**
34082
33841
  * Responsive design tokens — three-tier system.
@@ -34188,6 +33947,20 @@ var CHIP_BASE = "inline-flex items-center gap-1 rounded-full border px-1.5 py-0.
34188
33947
  var CHIP_ACTIVE = "border-primary/50 bg-primary/15 text-primary";
34189
33948
  var CHIP_INACTIVE = "border-border bg-surface text-foreground-subtle hover:bg-surface-hover";
34190
33949
  //#endregion
33950
+ //#region src/composites/data-table-layout.ts
33951
+ /**
33952
+ * At this many columns and above, `auto` switches a narrow viewport to cards.
33953
+ * Three columns still fit a phone at a readable size; four do not.
33954
+ */
33955
+ var CARD_MODE_MIN_COLUMNS = 4;
33956
+ function resolveTableLayout({ mode, columnCount, isNarrow }) {
33957
+ if (!isNarrow) return "table";
33958
+ if (mode === "scroll") return "table";
33959
+ if (mode === "cards") return "cards";
33960
+ if (columnCount === 0) return "table";
33961
+ return columnCount >= 4 ? "cards" : "table";
33962
+ }
33963
+ //#endregion
34191
33964
  //#region src/composites/setting-row.tsx
34192
33965
  function SettingRow({ label, children, actions, elements = "plain", className, valueClassName }) {
34193
33966
  const LabelTag = elements === "description" ? "dt" : "span";
@@ -34223,263 +33996,2418 @@ function SortHeaderButton({ label, active, dir, onSort }) {
34223
33996
  children: [/* @__PURE__ */ jsx("span", { children: label }), active ? dir === "asc" ? /* @__PURE__ */ jsx(ChevronUp, { className: "h-3 w-3 flex-shrink-0" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 flex-shrink-0" }) : /* @__PURE__ */ jsx(ChevronUp, { className: cn("h-3 w-3 flex-shrink-0 opacity-0 transition-opacity", "group-hover/sort:opacity-30") })]
34224
33997
  });
34225
33998
  }
34226
- //#endregion
34227
- //#region src/composites/table-sort.ts
33999
+ //#endregion
34000
+ //#region src/composites/table-sort.ts
34001
+ /**
34002
+ * The toggle rule, and the only copy of it.
34003
+ *
34004
+ * none → asc → desc → none, and a click on a DIFFERENT column starts that
34005
+ * column fresh at `asc`. `sameColumn` says whether the clicked column is the
34006
+ * one currently sorted; `currentDir` is its direction (`null` when nothing is
34007
+ * sorted). Returns the next direction, or `null` for "unsorted".
34008
+ *
34009
+ * Split out from {@link nextTableSort} so the device list — whose sort state
34010
+ * keys columns by `columnId`, not `columnKey` — can reuse the rule without a
34011
+ * cast and without a second copy of it.
34012
+ */
34013
+ function nextSortDirection(sameColumn, currentDir) {
34014
+ if (!sameColumn || currentDir === null) return "asc";
34015
+ return currentDir === "asc" ? "desc" : null;
34016
+ }
34017
+ /**
34018
+ * Toggle a column's sort on header click: none → asc → desc → none.
34019
+ * Clicking a DIFFERENT column starts it fresh at `asc`.
34020
+ */
34021
+ function nextTableSort(current, columnKey) {
34022
+ const dir = nextSortDirection(current !== null && current.columnKey === columnKey, current === null ? null : current.dir);
34023
+ return dir === null ? null : {
34024
+ columnKey,
34025
+ dir
34026
+ };
34027
+ }
34028
+ /**
34029
+ * Ascending comparison of two cell values.
34030
+ *
34031
+ * Numbers compare numerically and strings compare with `localeCompare` — a
34032
+ * numeric column must never be ordered as text ("100" before "9"), and a text
34033
+ * column must respect the locale's collation. A mixed column (one row a
34034
+ * number, another a string) falls back to string comparison of both, which is
34035
+ * the only total order available; it is not a shape any caller should build on
34036
+ * purpose.
34037
+ *
34038
+ * `null` is NOT handled here — {@link sortRowsByColumn} pulls it out first,
34039
+ * because its placement is direction-independent and a comparator that is
34040
+ * multiplied by the direction factor cannot express that.
34041
+ */
34042
+ function compareValues(a, b) {
34043
+ if (typeof a === "number" && typeof b === "number") return a - b;
34044
+ return String(a).localeCompare(String(b));
34045
+ }
34046
+ /**
34047
+ * Return a NEW array of `rows` ordered by `sort`.
34048
+ *
34049
+ * `valueOf` is the column's `sortValue` — it is called once per row per
34050
+ * comparison, so it must be cheap (read a field, do not format a string).
34051
+ * `sort === null` ⇒ a fresh shallow copy in the incoming order.
34052
+ */
34053
+ function sortRowsByColumn(rows, sort, valueOf) {
34054
+ if (sort === null) return [...rows];
34055
+ const factor = sort.dir === "asc" ? 1 : -1;
34056
+ return rows.toSorted((a, b) => {
34057
+ const av = valueOf(a);
34058
+ const bv = valueOf(b);
34059
+ if (av === null || bv === null) {
34060
+ if (av === bv) return 0;
34061
+ return av === null ? 1 : -1;
34062
+ }
34063
+ return factor * compareValues(av, bv);
34064
+ });
34065
+ }
34066
+ /** Derive the `<th>` `aria-sort` for `columnKey` from the table's sort state. */
34067
+ function ariaSortForColumn(sort, columnKey) {
34068
+ if (sort === null || sort.columnKey !== columnKey) return "none";
34069
+ return sort.dir === "asc" ? "ascending" : "descending";
34070
+ }
34071
+ //#endregion
34072
+ //#region src/composites/data-table.tsx
34073
+ /**
34074
+ * DataTable — shared responsive table for admin pages.
34075
+ *
34076
+ * Recipes baked in:
34077
+ * - `overflow-x-auto` wrapper so narrow viewports don't clip the
34078
+ * right edge (the rounded border survives the scroll).
34079
+ * - `min-w-[Npx]` on the inner table so columns don't squash below
34080
+ * readable width before the scroll kicks in.
34081
+ * - `whitespace-nowrap` on headers — column titles never wrap to
34082
+ * two lines (`AUTO-KILL` was the regression that triggered this).
34083
+ * - Themed `bg-surface` + `border-border` so contrast works in both
34084
+ * light and dark themes.
34085
+ *
34086
+ * Column model: caller supplies `columns: DataColumn<T>[]` and
34087
+ * `rows: T[]`. Each column declares `header`, optional `align`,
34088
+ * optional `nowrap`, and a `render(row) => ReactNode` that produces
34089
+ * the cell content.
34090
+ *
34091
+ * <DataTable
34092
+ * columns={[
34093
+ * { key: 'username', header: 'Username', render: (u) => u.username },
34094
+ * { key: 'role', header: 'Role', render: (u) => <RoleBadge .../> },
34095
+ * { key: 'actions', header: '', align: 'right', render: (u) => <Actions .../> },
34096
+ * ]}
34097
+ * rows={users}
34098
+ * minWidthPx={560}
34099
+ * />
34100
+ *
34101
+ * If finer header markup is needed, pass `headerRender` instead of
34102
+ * `header` (e.g. for a sortable click affordance).
34103
+ *
34104
+ * ── Narrow viewports ──────────────────────────────────────────────────────
34105
+ * The table stays a table. The wrapper is `overflow-x-auto`, so a phone
34106
+ * scrolls the columns sideways INSIDE the table — the page body never moves
34107
+ * sideways, and the cells stay one line (`whitespace-nowrap`). That is the
34108
+ * default (`mobileMode="scroll"`). Cards are opt-in via `mobileMode="cards"`;
34109
+ * `auto` still applies the old column-count rule for a surface that wants it.
34110
+ * See `resolveTableLayout`.
34111
+ *
34112
+ * ── Scrolling the BODY, not the page ──────────────────────────────────────
34113
+ * `maxBodyHeight` caps the table and scrolls its rows INSIDE the container
34114
+ * with the column header pinned. It is ONE prop, not two, because "this list
34115
+ * scrolls internally" and "its header stays visible" are the same decision: a
34116
+ * capped table whose header scrolls away is a list of unlabelled cells.
34117
+ *
34118
+ * Use it wherever the row count is open-ended (rules, targets, delivery
34119
+ * history) so the page keeps its own header, filters and actions in view while
34120
+ * the operator moves through the rows.
34121
+ */
34122
+ /**
34123
+ * The default cap for an internally-scrolling table.
34124
+ *
34125
+ * `60vh` leaves the page header, its filters and its actions on screen at
34126
+ * every viewport while still showing ~10 rows — and it is a fraction, so it
34127
+ * does not need a breakpoint of its own.
34128
+ */
34129
+ var TABLE_SCROLL_MAX_H = "max-h-[60vh]";
34130
+ var ALIGN_CLASS = {
34131
+ left: "text-left",
34132
+ right: "text-right",
34133
+ center: "text-center"
34134
+ };
34135
+ function DataTable({ columns, rows, rowKey, onRowClick, minWidthPx = 480, emptyMessage, className, bordered = true, rowClassName, mobileMode = "scroll", maxBodyHeight, defaultSort }) {
34136
+ const isNarrow = useIsMobile();
34137
+ const [sort, setSort] = useState(defaultSort ?? null);
34138
+ const layout = resolveTableLayout({
34139
+ mode: mobileMode,
34140
+ columnCount: columns.length,
34141
+ isNarrow
34142
+ });
34143
+ const scrolls = maxBodyHeight !== void 0;
34144
+ const sortedColumn = useMemo(() => sort === null ? void 0 : columns.find((col) => col.key === sort.columnKey && col.sortValue !== void 0), [columns, sort]);
34145
+ const sortValue = sortedColumn?.sortValue;
34146
+ const displayRows = useMemo(() => sortValue === void 0 ? rows : sortRowsByColumn(rows, sort, sortValue), [
34147
+ rows,
34148
+ sort,
34149
+ sortValue
34150
+ ]);
34151
+ const activeSort = sortedColumn === void 0 ? null : sort;
34152
+ if (rows.length === 0 && emptyMessage) return /* @__PURE__ */ jsx("div", {
34153
+ className: `rounded-lg ${bordered ? "border border-border" : ""} bg-surface px-3 py-4 text-xs text-foreground-subtle text-center ${className ?? ""}`,
34154
+ children: emptyMessage
34155
+ });
34156
+ if (rows.length === 0) return null;
34157
+ if (layout === "cards") return /* @__PURE__ */ jsx("div", {
34158
+ className: `space-y-2 ${scrolls ? `${maxBodyHeight} overflow-y-auto` : ""} ${className ?? ""}`,
34159
+ children: displayRows.map((row, rowIndex) => /* @__PURE__ */ jsx(DataTableCard, {
34160
+ row,
34161
+ rowIndex,
34162
+ columns,
34163
+ bordered,
34164
+ onRowClick,
34165
+ rowClassName
34166
+ }, rowKey ? rowKey(row, rowIndex) : rowIndex))
34167
+ });
34168
+ return /* @__PURE__ */ jsx("div", {
34169
+ className: [
34170
+ "rounded-lg",
34171
+ bordered ? "border border-border" : "",
34172
+ "bg-surface overflow-x-auto",
34173
+ scrolls ? `${maxBodyHeight} overflow-y-auto` : "",
34174
+ className ?? ""
34175
+ ].filter(Boolean).join(" "),
34176
+ children: /* @__PURE__ */ jsxs("table", {
34177
+ className: `w-full text-xs ${scrolls ? "border-separate border-spacing-0" : ""}`,
34178
+ style: { minWidth: `${minWidthPx}px` },
34179
+ children: [/* @__PURE__ */ jsx("thead", {
34180
+ className: "bg-surface",
34181
+ children: /* @__PURE__ */ jsx("tr", { children: columns.map((col) => {
34182
+ const align = col.align ?? "left";
34183
+ const nowrap = col.nowrap ?? true;
34184
+ const sortable = col.sortValue !== void 0;
34185
+ const isSorted = activeSort !== null && activeSort.columnKey === col.key;
34186
+ return /* @__PURE__ */ jsx("th", {
34187
+ "aria-sort": sortable ? ariaSortForColumn(activeSort, col.key) : void 0,
34188
+ title: sortable ? void 0 : col.notSortableReason,
34189
+ className: [
34190
+ "px-3 py-2 text-foreground-subtle font-medium bg-surface border-b border-border",
34191
+ scrolls ? "sticky top-0 z-10" : "",
34192
+ ALIGN_CLASS[align],
34193
+ nowrap ? "whitespace-nowrap" : "",
34194
+ col.width ?? ""
34195
+ ].filter(Boolean).join(" "),
34196
+ children: sortable ? /* @__PURE__ */ jsx(SortHeaderButton, {
34197
+ label: col.headerRender ? col.headerRender() : col.header,
34198
+ active: isSorted,
34199
+ dir: isSorted && activeSort !== null ? activeSort.dir : "asc",
34200
+ onSort: () => setSort((prev) => nextTableSort(prev, col.key))
34201
+ }) : col.headerRender ? col.headerRender() : col.header
34202
+ }, col.key);
34203
+ }) })
34204
+ }), /* @__PURE__ */ jsx("tbody", { children: displayRows.map((row, rowIndex) => {
34205
+ const key = rowKey ? rowKey(row, rowIndex) : rowIndex;
34206
+ const extra = rowClassName?.(row, rowIndex) ?? "";
34207
+ const interactive = onRowClick !== void 0;
34208
+ return /* @__PURE__ */ jsx("tr", {
34209
+ onClick: interactive ? () => onRowClick(row, rowIndex) : void 0,
34210
+ className: [interactive ? "cursor-pointer hover:bg-primary/5" : "hover:bg-primary/5", extra].filter(Boolean).join(" "),
34211
+ children: columns.map((col) => {
34212
+ const align = col.align ?? "left";
34213
+ const nowrap = col.nowrap ?? true;
34214
+ return /* @__PURE__ */ jsx("td", {
34215
+ className: [
34216
+ "px-3 py-2 text-foreground border-b border-border",
34217
+ ALIGN_CLASS[align],
34218
+ nowrap ? "whitespace-nowrap" : ""
34219
+ ].filter(Boolean).join(" "),
34220
+ children: col.render(row, rowIndex)
34221
+ }, col.key);
34222
+ })
34223
+ }, key);
34224
+ }) })]
34225
+ })
34226
+ });
34227
+ }
34228
+ /**
34229
+ * One row as a card. Columns that carry a plain-text `header` become labelled
34230
+ * `SettingRow`s — the label is the column title and the value gets the full
34231
+ * card width. Columns with no text header (an actions column, a
34232
+ * `headerRender`-only column) have no label to show, so they render as a
34233
+ * full-width strip at the foot of the card.
34234
+ */
34235
+ function DataTableCard({ row, rowIndex, columns, bordered, onRowClick, rowClassName }) {
34236
+ const labelled = columns.filter((col) => col.header !== void 0 && col.header !== "");
34237
+ const unlabelled = columns.filter((col) => col.header === void 0 || col.header === "");
34238
+ const interactive = onRowClick !== void 0;
34239
+ const extra = rowClassName?.(row, rowIndex) ?? "";
34240
+ return /* @__PURE__ */ jsxs("div", {
34241
+ "data-data-table-card": "",
34242
+ onClick: interactive ? () => onRowClick(row, rowIndex) : void 0,
34243
+ className: [
34244
+ "rounded-lg bg-surface px-3 py-1.5 divide-y divide-border-subtle",
34245
+ bordered ? "border border-border" : "",
34246
+ interactive ? "cursor-pointer hover:bg-primary/5" : "",
34247
+ extra
34248
+ ].filter(Boolean).join(" "),
34249
+ children: [labelled.map((col) => /* @__PURE__ */ jsx(SettingRow, {
34250
+ label: col.header,
34251
+ children: col.render(row, rowIndex)
34252
+ }, col.key)), unlabelled.length > 0 && /* @__PURE__ */ jsx("div", {
34253
+ className: "flex flex-wrap items-center justify-end gap-2 py-1.5",
34254
+ children: unlabelled.map((col) => /* @__PURE__ */ jsx("div", { children: col.render(row, rowIndex) }, col.key))
34255
+ })]
34256
+ });
34257
+ }
34258
+ //#endregion
34259
+ //#region src/composites/key-value-list.tsx
34260
+ /**
34261
+ * A `<dl>` of label/value rows. Layout (including the L1 stacking) is owned by
34262
+ * `SettingRow`; this composite only supplies the description-list semantics.
34263
+ *
34264
+ * The rows used to be `flex items-center h-7` with a `w-1/3` term, so a label
34265
+ * could neither wrap nor stack: on a phone it took a third of the width and
34266
+ * the value took whatever was left.
34267
+ */
34268
+ function KeyValueList({ items, className }) {
34269
+ return /* @__PURE__ */ jsx("dl", {
34270
+ className: cn("flex flex-col", className),
34271
+ children: items.map((item) => /* @__PURE__ */ jsx(SettingRow, {
34272
+ elements: "description",
34273
+ label: item.key,
34274
+ children: item.value
34275
+ }, item.key))
34276
+ });
34277
+ }
34278
+ //#endregion
34279
+ //#region src/composites/section-card.tsx
34280
+ function SectionCard({ headingId, title, icon: Icon, actions, testId, children }) {
34281
+ return /* @__PURE__ */ jsxs("section", {
34282
+ "aria-labelledby": headingId,
34283
+ className: SECTION_CARD,
34284
+ "data-testid": testId,
34285
+ children: [/* @__PURE__ */ jsxs("header", {
34286
+ className: `${SECTION_HEADER} flex min-h-9 flex-wrap items-center gap-2`,
34287
+ children: [
34288
+ Icon !== void 0 && /* @__PURE__ */ jsx(Icon, {
34289
+ className: "h-3.5 w-3.5 shrink-0 text-foreground-subtle",
34290
+ "aria-hidden": true
34291
+ }),
34292
+ /* @__PURE__ */ jsx("h2", {
34293
+ id: headingId,
34294
+ className: `${TEXT_SECTION_LABEL} min-w-0 flex-1`,
34295
+ children: title
34296
+ }),
34297
+ actions
34298
+ ]
34299
+ }), children]
34300
+ });
34301
+ }
34302
+ //#endregion
34303
+ //#region src/composites/webrtc-diagnostics/ice-candidates.ts
34304
+ var CANDIDATE_TYPES = [
34305
+ "host",
34306
+ "srflx",
34307
+ "prflx",
34308
+ "relay"
34309
+ ];
34310
+ function intOrNull(token) {
34311
+ if (token === void 0 || !/^\d+$/.test(token)) return null;
34312
+ const n = Number(token);
34313
+ return Number.isSafeInteger(n) ? n : null;
34314
+ }
34315
+ function familyOf(address) {
34316
+ if (address.length === 0) return "unknown";
34317
+ if (address.toLowerCase().endsWith(".local")) return "mdns";
34318
+ if (address.includes(":")) return "ipv6";
34319
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(address)) return "ipv4";
34320
+ return "unknown";
34321
+ }
34322
+ function ipv4Octets(address) {
34323
+ const parts = address.split(".");
34324
+ if (parts.length !== 4) return null;
34325
+ const octets = parts.map((p) => Number(p));
34326
+ return octets.every((o) => Number.isInteger(o) && o >= 0 && o <= 255) ? octets : null;
34327
+ }
34328
+ function ipv4Scope(address) {
34329
+ const o = ipv4Octets(address);
34330
+ if (o === null) return "unknown";
34331
+ const [a = -1, b = -1] = o;
34332
+ if (a === 127) return "loopback";
34333
+ if (a === 169 && b === 254) return "link-local";
34334
+ if (a === 10 || a === 192 && b === 168 || a === 172 && b >= 16 && b <= 31) return "lan";
34335
+ if (a === 100 && b >= 64 && b <= 127) return "tailscale";
34336
+ return "public";
34337
+ }
34338
+ function ipv6Scope(address) {
34339
+ const lower = address.toLowerCase();
34340
+ if (lower === "::1") return "loopback";
34341
+ if (/^fe[89ab]/.test(lower)) return "link-local";
34342
+ if (lower.startsWith("fd7a:115c:a1e0:")) return "tailscale";
34343
+ if (/^f[cd]/.test(lower)) return "lan";
34344
+ return "public";
34345
+ }
34346
+ function classifyAddressScope(address) {
34347
+ switch (familyOf(address)) {
34348
+ case "mdns": return "mdns";
34349
+ case "ipv4": return ipv4Scope(address);
34350
+ case "ipv6": return ipv6Scope(address);
34351
+ default: return "unknown";
34352
+ }
34353
+ }
34354
+ /** Parse one candidate attribute (`candidate:…`, with or without `a=`). */
34355
+ function parseIceCandidate(line) {
34356
+ const raw = line.trim().replace(/^a=/, "");
34357
+ if (!raw.startsWith("candidate:")) return null;
34358
+ const parts = raw.slice(10).split(/\s+/);
34359
+ const address = parts[4] ?? "";
34360
+ const typIdx = parts.indexOf("typ");
34361
+ const typeToken = typIdx >= 0 ? parts[typIdx + 1] : void 0;
34362
+ const type = CANDIDATE_TYPES.find((t) => t === typeToken) ?? "unknown";
34363
+ const raddrIdx = parts.indexOf("raddr");
34364
+ const rportIdx = parts.indexOf("rport");
34365
+ const protocol = parts[2]?.toLowerCase();
34366
+ return {
34367
+ type,
34368
+ protocol: protocol !== void 0 && protocol.length > 0 ? protocol : null,
34369
+ address: address.length > 0 ? address : null,
34370
+ port: intOrNull(parts[5]),
34371
+ relatedAddress: raddrIdx >= 0 ? parts[raddrIdx + 1] ?? null : null,
34372
+ relatedPort: rportIdx >= 0 ? intOrNull(parts[rportIdx + 1]) : null,
34373
+ family: familyOf(address),
34374
+ scope: classifyAddressScope(address),
34375
+ raw
34376
+ };
34377
+ }
34378
+ /** Every `a=candidate` line of an SDP, in order. */
34379
+ function candidateLinesFromSdp(sdp) {
34380
+ return sdp.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.startsWith("a=candidate:"));
34381
+ }
34382
+ function candidateKey(c) {
34383
+ return `${c.type}|${c.protocol ?? "?"}|${c.address ?? "?"}|${String(c.port ?? "?")}`;
34384
+ }
34385
+ var PRIVATE_SCOPES = new Set([
34386
+ "loopback",
34387
+ "link-local",
34388
+ "lan",
34389
+ "tailscale",
34390
+ "mdns"
34391
+ ]);
34392
+ function emptyCounts() {
34393
+ return {
34394
+ host: 0,
34395
+ srflx: 0,
34396
+ prflx: 0,
34397
+ relay: 0,
34398
+ unknown: 0
34399
+ };
34400
+ }
34401
+ function countCandidateTypes(candidates) {
34402
+ const counts = emptyCounts();
34403
+ for (const c of candidates) counts[c.type] += 1;
34404
+ return counts;
34405
+ }
34406
+ function reachabilityFlag(answered, candidates) {
34407
+ if (!answered && candidates.length === 0) return "unknown";
34408
+ if (candidates.length === 0) return "none";
34409
+ if (candidates.some((c) => c.type === "srflx" || c.type === "relay" || c.type === "host" && c.scope === "public")) return "public-path";
34410
+ return candidates.every((c) => PRIVATE_SCOPES.has(c.scope)) ? "private-only" : "public-path";
34411
+ }
34412
+ /** The hub's offered candidates: answer SDP plus trickle, deduplicated. */
34413
+ function summarizeHubCandidates(input) {
34414
+ const seen = /* @__PURE__ */ new Set();
34415
+ const candidates = [];
34416
+ const add = (line) => {
34417
+ const c = parseIceCandidate(line);
34418
+ if (c === null) return false;
34419
+ const key = candidateKey(c);
34420
+ if (seen.has(key)) return false;
34421
+ seen.add(key);
34422
+ candidates.push(c);
34423
+ return true;
34424
+ };
34425
+ let fromSdp = 0;
34426
+ for (const line of candidateLinesFromSdp(input.sdpAnswer ?? "")) if (add(line)) fromSdp += 1;
34427
+ let fromTrickle = 0;
34428
+ for (const line of input.trickled) if (add(line)) fromTrickle += 1;
34429
+ return {
34430
+ candidates,
34431
+ fromSdp,
34432
+ fromTrickle,
34433
+ countByType: countCandidateTypes(candidates),
34434
+ hasSrflx: candidates.some((c) => c.type === "srflx"),
34435
+ hasRelay: candidates.some((c) => c.type === "relay"),
34436
+ hasPublicHost: candidates.some((c) => c.type === "host" && c.scope === "public"),
34437
+ flag: reachabilityFlag(input.sdpAnswer !== null, candidates),
34438
+ trickleDone: input.trickleDone
34439
+ };
34440
+ }
34441
+ //#endregion
34442
+ //#region src/composites/webrtc-diagnostics/endpoint-probe.ts
34443
+ /**
34444
+ * Browser-side probes behind "Test each endpoint" and the NAT mapping check.
34445
+ *
34446
+ * Everything runs in the operator's browser against real `RTCPeerConnection`s;
34447
+ * the only server round-trip is fetching the ICE server list.
34448
+ *
34449
+ * - `gatherCandidates` — one peer connection, a given server set: which
34450
+ * candidate types it produces, which server URL produced each (the ICE
34451
+ * event's `url`) and, for a relay, the transport to the TURN server
34452
+ * (`relayProtocol`: udp / tcp / tls).
34453
+ * - `relayLoopback` — two peer connections in this page, both forced to
34454
+ * `relay`: the ONLY way they connect is through the TURN server, so a
34455
+ * connect proves allocation and forwarding work, and the selected pair
34456
+ * gives the relay RTT and the local relay protocol from `getStats()`.
34457
+ * (A same-page loopback with policy `all` proves nothing — the two peers
34458
+ * connect host↔host — which is why it is not offered.)
34459
+ *
34460
+ * Credentials are attached to `turn:`/`turns:` entries only and never leave
34461
+ * this module: nothing here logs or returns them.
34462
+ */
34463
+ function toRtcIceServers(servers) {
34464
+ return servers.map((s) => {
34465
+ if (/^turns?:/i.test(s.url) && s.username !== void 0 && s.credential !== void 0) return {
34466
+ urls: s.url,
34467
+ username: s.username,
34468
+ credential: s.credential
34469
+ };
34470
+ return { urls: s.url };
34471
+ });
34472
+ }
34473
+ /** `RTCPeerConnectionIceEvent.url` — a real field missing from this lib.dom. */
34474
+ function iceEventUrl(e) {
34475
+ if ("url" in e && typeof e.url === "string" && e.url.length > 0) return e.url;
34476
+ return null;
34477
+ }
34478
+ /** `RTCIceCandidate.relayProtocol` — Chromium and WebKit report it; lib.dom does not type it. */
34479
+ function candidateRelayProtocol(c) {
34480
+ if ("relayProtocol" in c && typeof c.relayProtocol === "string" && c.relayProtocol.length > 0) return c.relayProtocol;
34481
+ return null;
34482
+ }
34483
+ /** One local candidate from an ICE event, or null for the end-of-candidates event. */
34484
+ function probedCandidateFromEvent(e, tMs) {
34485
+ const raw = e.candidate?.candidate;
34486
+ if (e.candidate === null || raw === void 0 || raw.length === 0) return null;
34487
+ const candidate = parseIceCandidate(raw);
34488
+ if (candidate === null) return null;
34489
+ return {
34490
+ candidate,
34491
+ url: iceEventUrl(e),
34492
+ relayProtocol: candidateRelayProtocol(e.candidate),
34493
+ tMs
34494
+ };
34495
+ }
34496
+ function errorText$1(err) {
34497
+ return err instanceof Error ? err.message : String(err);
34498
+ }
34499
+ async function gatherCandidates(servers, policy, timeoutMs = 8e3) {
34500
+ const start = performance.now();
34501
+ const candidates = [];
34502
+ const pc = new RTCPeerConnection({
34503
+ iceServers: toRtcIceServers(servers),
34504
+ iceTransportPolicy: policy
34505
+ });
34506
+ try {
34507
+ pc.createDataChannel("probe");
34508
+ const done = new Promise((resolve) => {
34509
+ const timer = setTimeout(() => resolve(true), timeoutMs);
34510
+ pc.addEventListener("icecandidate", (e) => {
34511
+ if (e.candidate === null) {
34512
+ clearTimeout(timer);
34513
+ resolve(false);
34514
+ return;
34515
+ }
34516
+ const probed = probedCandidateFromEvent(e, performance.now() - start);
34517
+ if (probed !== null) candidates.push(probed);
34518
+ });
34519
+ });
34520
+ await pc.setLocalDescription(await pc.createOffer());
34521
+ const timedOut = await done;
34522
+ return {
34523
+ candidates,
34524
+ durationMs: performance.now() - start,
34525
+ timedOut,
34526
+ error: null
34527
+ };
34528
+ } catch (err) {
34529
+ return {
34530
+ candidates,
34531
+ durationMs: performance.now() - start,
34532
+ timedOut: false,
34533
+ error: errorText$1(err)
34534
+ };
34535
+ } finally {
34536
+ pc.close();
34537
+ }
34538
+ }
34539
+ function selectedPairFacts(report) {
34540
+ const byId = /* @__PURE__ */ new Map();
34541
+ const pairs = [];
34542
+ const selected = { id: null };
34543
+ report.forEach((raw) => {
34544
+ const s = raw;
34545
+ if (s.id !== void 0) byId.set(s.id, s);
34546
+ if (s.type === "candidate-pair") pairs.push(s);
34547
+ if (s.type === "transport" && s.selectedCandidatePairId !== void 0) selected.id = s.selectedCandidatePairId;
34548
+ });
34549
+ const pair = pairs.find((p) => selected.id !== null && p.id === selected.id) ?? pairs.find((p) => p.selected === true) ?? pairs.find((p) => p.nominated === true && p.state === "succeeded") ?? null;
34550
+ if (pair === null) return {
34551
+ rttMs: null,
34552
+ relayProtocol: null
34553
+ };
34554
+ const local = pair.localCandidateId !== void 0 ? byId.get(pair.localCandidateId) : void 0;
34555
+ return {
34556
+ rttMs: typeof pair.currentRoundTripTime === "number" ? Math.round(pair.currentRoundTripTime * 1e3) : null,
34557
+ relayProtocol: typeof local?.relayProtocol === "string" ? local.relayProtocol : null
34558
+ };
34559
+ }
34560
+ async function relayLoopback(servers, timeoutMs = 1e4) {
34561
+ const start = performance.now();
34562
+ const config = {
34563
+ iceServers: toRtcIceServers(servers),
34564
+ iceTransportPolicy: "relay"
34565
+ };
34566
+ const pcA = new RTCPeerConnection(config);
34567
+ const pcB = new RTCPeerConnection(config);
34568
+ try {
34569
+ pcA.createDataChannel("probe");
34570
+ pcA.addEventListener("icecandidate", (e) => {
34571
+ if (e.candidate) pcB.addIceCandidate(e.candidate).catch(() => void 0);
34572
+ });
34573
+ pcB.addEventListener("icecandidate", (e) => {
34574
+ if (e.candidate) pcA.addIceCandidate(e.candidate).catch(() => void 0);
34575
+ });
34576
+ const connected = new Promise((resolve) => {
34577
+ const timer = setTimeout(() => resolve(false), timeoutMs);
34578
+ pcA.addEventListener("iceconnectionstatechange", () => {
34579
+ const st = pcA.iceConnectionState;
34580
+ if (st === "connected" || st === "completed") {
34581
+ clearTimeout(timer);
34582
+ resolve(true);
34583
+ } else if (st === "failed" || st === "closed") {
34584
+ clearTimeout(timer);
34585
+ resolve(false);
34586
+ }
34587
+ });
34588
+ });
34589
+ const offer = await pcA.createOffer();
34590
+ await pcA.setLocalDescription(offer);
34591
+ await pcB.setRemoteDescription(offer);
34592
+ const answer = await pcB.createAnswer();
34593
+ await pcB.setLocalDescription(answer);
34594
+ await pcA.setRemoteDescription(answer);
34595
+ const ok = await connected;
34596
+ const facts = ok ? selectedPairFacts(await pcA.getStats()) : {
34597
+ rttMs: null,
34598
+ relayProtocol: null
34599
+ };
34600
+ return {
34601
+ connected: ok,
34602
+ state: pcA.iceConnectionState,
34603
+ rttMs: facts.rttMs,
34604
+ relayProtocol: facts.relayProtocol,
34605
+ durationMs: performance.now() - start,
34606
+ error: null
34607
+ };
34608
+ } catch (err) {
34609
+ return {
34610
+ connected: false,
34611
+ state: pcA.iceConnectionState,
34612
+ rttMs: null,
34613
+ relayProtocol: null,
34614
+ durationMs: performance.now() - start,
34615
+ error: errorText$1(err)
34616
+ };
34617
+ } finally {
34618
+ pcA.close();
34619
+ pcB.close();
34620
+ }
34621
+ }
34622
+ //#endregion
34623
+ //#region src/composites/webrtc-diagnostics/nat-mapping.ts
34624
+ function isHiddenBase(o) {
34625
+ return o.relatedAddress === null || o.relatedAddress === "0.0.0.0" || o.relatedAddress === "::" || o.relatedPort === null || o.relatedPort === 0;
34626
+ }
34627
+ function baseKey(o) {
34628
+ return isHiddenBase(o) ? `public:${o.address}` : `base:${o.relatedAddress ?? "?"}:${String(o.relatedPort ?? "?")}`;
34629
+ }
34630
+ function unknown(reason, mappings = []) {
34631
+ return {
34632
+ kind: "unknown",
34633
+ confidence: "none",
34634
+ reason,
34635
+ mappings
34636
+ };
34637
+ }
34638
+ /**
34639
+ * The distinct STUN destinations (`stun:host:port`) among the given URLs —
34640
+ * the number the check needs to be at least two. TURN URLs are excluded:
34641
+ * this check is about plain STUN binding.
34642
+ */
34643
+ function distinctStunDestinations(urls) {
34644
+ const out = [];
34645
+ for (const url of urls) {
34646
+ const m = /^stuns?:([^?]+)/i.exec(url.trim());
34647
+ if (m === null) continue;
34648
+ const dest = `stun:${(m[1] ?? "").toLowerCase()}`;
34649
+ if (!out.includes(dest)) out.push(dest);
34650
+ }
34651
+ return out;
34652
+ }
34653
+ function compareSrflxMappings(observations, stunDestinationsQueried) {
34654
+ const udp = observations.filter((o) => o.protocol.toLowerCase() === "udp");
34655
+ if (stunDestinationsQueried < 2) return unknown("needs two STUN destinations to compare; only one was queried");
34656
+ if (udp.length === 0) return unknown("no server-reflexive candidate: STUN unreachable or blocked from this browser");
34657
+ const groups = /* @__PURE__ */ new Map();
34658
+ for (const o of udp) {
34659
+ const key = baseKey(o);
34660
+ groups.set(key, [...groups.get(key) ?? [], o]);
34661
+ }
34662
+ const mappings = [...new Set(udp.map((o) => `${o.address}:${String(o.port)}`))];
34663
+ for (const group of groups.values()) {
34664
+ const distinct = new Set(group.map((o) => `${o.address}:${String(o.port)}`));
34665
+ if (distinct.size > 1) return {
34666
+ kind: "endpoint-dependent",
34667
+ confidence: "confirmed",
34668
+ reason: `one local socket got ${String(distinct.size)} different public mappings — symmetric NAT: only a TURN relay reaches this browser`,
34669
+ mappings
34670
+ };
34671
+ }
34672
+ const confirmed = [...groups.values()].some((group) => new Set(group.map((o) => o.url).filter((u) => u !== null)).size >= 2);
34673
+ return {
34674
+ kind: "endpoint-independent",
34675
+ confidence: confirmed ? "confirmed" : "likely",
34676
+ reason: confirmed ? "two STUN destinations saw the same public mapping" : "one public mapping across the STUN destinations (the browser drops an identical second candidate, so the second answer cannot be told from no answer)",
34677
+ mappings
34678
+ };
34679
+ }
34680
+ //#endregion
34681
+ //#region src/composites/webrtc-diagnostics/endpoint-test.ts
34682
+ /**
34683
+ * "Test each endpoint" and the NAT mapping check — the orchestration around
34684
+ * the browser probes, with the row derivation kept pure so it is tested
34685
+ * without a browser.
34686
+ */
34687
+ /** Queried in addition to the configured STUN servers until two distinct destinations exist. */
34688
+ var FALLBACK_STUN_URLS = ["stun:stun.l.google.com:19302", "stun:stun1.l.google.com:19302"];
34689
+ function schemeOf(url) {
34690
+ const lower = url.toLowerCase();
34691
+ if (lower.startsWith("turns:")) return "turns";
34692
+ if (lower.startsWith("turn:")) return "turn";
34693
+ if (lower.startsWith("stun:") || lower.startsWith("stuns:")) return "stun";
34694
+ return "other";
34695
+ }
34696
+ function transportOf(url) {
34697
+ return /[?&]transport=([a-z]+)/i.exec(url)?.[1]?.toLowerCase() ?? null;
34698
+ }
34699
+ /** One row per URL: an entry's `urls` may be a string or a list. */
34700
+ function flattenIceServers(servers) {
34701
+ const out = [];
34702
+ for (const s of servers) {
34703
+ const urls = typeof s.urls === "string" ? [s.urls] : s.urls;
34704
+ for (const url of urls) {
34705
+ if (out.some((e) => e.id === url)) continue;
34706
+ out.push({
34707
+ id: url,
34708
+ url,
34709
+ ...s.username !== void 0 ? { username: s.username } : {},
34710
+ ...s.credential !== void 0 ? { credential: s.credential } : {},
34711
+ scheme: schemeOf(url),
34712
+ transport: transportOf(url),
34713
+ hasCredentials: s.credential !== void 0
34714
+ });
34715
+ }
34716
+ }
34717
+ return out;
34718
+ }
34719
+ function endpointRowFromProbe(endpoint, gather, loopback) {
34720
+ const srflx = gather.candidates.find((c) => c.candidate.type === "srflx");
34721
+ const relay = gather.candidates.find((c) => c.candidate.type === "relay");
34722
+ const mapped = srflx !== void 0 && srflx.candidate.address !== null && srflx.candidate.port !== null ? `${srflx.candidate.address}:${String(srflx.candidate.port)}` : null;
34723
+ return {
34724
+ id: endpoint.id,
34725
+ url: endpoint.url,
34726
+ scheme: endpoint.scheme,
34727
+ transport: endpoint.transport,
34728
+ srflx: mapped,
34729
+ relay: relay !== void 0,
34730
+ relayProtocol: loopback?.relayProtocol ?? relay?.relayProtocol ?? null,
34731
+ eventUrl: relay?.url ?? srflx?.url ?? null,
34732
+ relayConnected: loopback?.connected ?? null,
34733
+ rttMs: loopback?.rttMs ?? null,
34734
+ timedOut: gather.timedOut,
34735
+ error: gather.error ?? loopback?.error ?? null
34736
+ };
34737
+ }
34738
+ var GATHER_TIMEOUT_MS = 6e3;
34739
+ var LOOPBACK_TIMEOUT_MS = 8e3;
34740
+ function configOf(e) {
34741
+ return [{
34742
+ url: e.url,
34743
+ ...e.username !== void 0 ? { username: e.username } : {},
34744
+ ...e.credential !== void 0 ? { credential: e.credential } : {}
34745
+ }];
34746
+ }
34747
+ /** Gather against ONE endpoint; for a TURN endpoint, also prove the relay forwards. */
34748
+ async function testEndpoint(endpoint) {
34749
+ const cfg = configOf(endpoint);
34750
+ return endpointRowFromProbe(endpoint, await gatherCandidates(cfg, "all", GATHER_TIMEOUT_MS), endpoint.scheme === "turn" || endpoint.scheme === "turns" ? await relayLoopback(cfg, LOOPBACK_TIMEOUT_MS) : null);
34751
+ }
34752
+ /** The STUN destinations the NAT check queries: configured first, public fallbacks to reach two. */
34753
+ function natCheckDestinations(endpoints) {
34754
+ const out = [...distinctStunDestinations(endpoints.map((e) => e.url))];
34755
+ for (const url of distinctStunDestinations(FALLBACK_STUN_URLS)) {
34756
+ if (out.length >= 2) break;
34757
+ if (!out.includes(url)) out.push(url);
34758
+ }
34759
+ return out;
34760
+ }
34761
+ function srflxObservations(gather) {
34762
+ const out = [];
34763
+ for (const c of gather.candidates) {
34764
+ const p = c.candidate;
34765
+ if (p.type !== "srflx" || p.address === null || p.port === null || p.protocol === null) continue;
34766
+ out.push({
34767
+ address: p.address,
34768
+ port: p.port,
34769
+ protocol: p.protocol,
34770
+ relatedAddress: p.relatedAddress,
34771
+ relatedPort: p.relatedPort,
34772
+ url: c.url
34773
+ });
34774
+ }
34775
+ return out;
34776
+ }
34777
+ async function runNatCheck(endpoints) {
34778
+ const destinations = natCheckDestinations(endpoints);
34779
+ const gather = await gatherCandidates(destinations.map((url) => ({ url })), "all", GATHER_TIMEOUT_MS);
34780
+ return {
34781
+ destinations,
34782
+ verdict: compareSrflxMappings(srflxObservations(gather), destinations.length),
34783
+ error: gather.error
34784
+ };
34785
+ }
34786
+ //#endregion
34787
+ //#region src/composites/webrtc-diagnostics/format.ts
34788
+ /**
34789
+ * Rendering helpers for the WebRTC diagnostics: an unmeasured value is the
34790
+ * word "unknown", never a dash that reads as zero and never a 0 (D393).
34791
+ */
34792
+ var UNKNOWN_TEXT = "unknown";
34793
+ function formatMaybe(value, render) {
34794
+ return value === null ? UNKNOWN_TEXT : render(value);
34795
+ }
34796
+ var fmtMs = (v) => formatMaybe(v, (n) => `${String(Math.round(n))} ms`);
34797
+ var fmtCount = (v) => formatMaybe(v, (n) => n.toLocaleString());
34798
+ var fmtBytes = (v) => formatMaybe(v, (n) => {
34799
+ if (n < 1024) return `${String(n)} B`;
34800
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KiB`;
34801
+ return `${(n / (1024 * 1024)).toFixed(2)} MiB`;
34802
+ });
34803
+ var fmtPercent = (v) => formatMaybe(v, (n) => `${n.toFixed(1)} %`);
34804
+ var fmtText = (v) => formatMaybe(v, (s) => s);
34805
+ //#endregion
34806
+ //#region src/composites/webrtc-diagnostics/EndpointTestCard.tsx
34807
+ /**
34808
+ * "ICE servers" — the live ICE server list (`turnProvider.getTurnServers`, the
34809
+ * same union every viewer is handed) probed endpoint by endpoint from THIS
34810
+ * browser, plus the NAT mapping check.
34811
+ *
34812
+ * Owns no hub data beyond the list it is given through `loadServers`.
34813
+ * Credentials are passed to the probes and never rendered: a row shows `auth`.
34814
+ */
34815
+ var SCHEME_VARIANT = {
34816
+ stun: "info",
34817
+ turn: "warning",
34818
+ turns: "success",
34819
+ other: "default"
34820
+ };
34821
+ function errorText(err) {
34822
+ return err instanceof Error ? err.message : String(err);
34823
+ }
34824
+ function yesNo(value, yes, no) {
34825
+ if (value === null) return /* @__PURE__ */ jsx("span", {
34826
+ className: "text-foreground-subtle",
34827
+ children: UNKNOWN_TEXT
34828
+ });
34829
+ return /* @__PURE__ */ jsx("span", {
34830
+ className: value ? "text-success" : "text-danger",
34831
+ children: value ? yes : no
34832
+ });
34833
+ }
34834
+ var RESULT_COLUMNS = [
34835
+ {
34836
+ key: "endpoint",
34837
+ header: "Endpoint",
34838
+ nowrap: false,
34839
+ render: (r) => /* @__PURE__ */ jsx("span", {
34840
+ className: "break-all font-mono text-[11px]",
34841
+ children: r.url
34842
+ })
34843
+ },
34844
+ {
34845
+ key: "srflx",
34846
+ header: "Mapped (srflx)",
34847
+ render: (r) => /* @__PURE__ */ jsx("span", {
34848
+ className: "font-mono text-[11px]",
34849
+ children: r.srflx ?? "none"
34850
+ })
34851
+ },
34852
+ {
34853
+ key: "relay",
34854
+ header: "Relay",
34855
+ render: (r) => r.scheme === "stun" ? /* @__PURE__ */ jsx("span", {
34856
+ className: "text-foreground-subtle",
34857
+ children: "n/a"
34858
+ }) : yesNo(r.relay, "allocated", "none")
34859
+ },
34860
+ {
34861
+ key: "proto",
34862
+ header: "Relay protocol",
34863
+ render: (r) => r.scheme === "stun" ? "n/a" : fmtText(r.relayProtocol)
34864
+ },
34865
+ {
34866
+ key: "connect",
34867
+ header: "Relay connect",
34868
+ render: (r) => r.relayConnected === null ? /* @__PURE__ */ jsx("span", {
34869
+ className: "text-foreground-subtle",
34870
+ children: "n/a"
34871
+ }) : yesNo(r.relayConnected, "connected", "failed")
34872
+ },
34873
+ {
34874
+ key: "rtt",
34875
+ header: "RTT",
34876
+ align: "right",
34877
+ render: (r) => fmtMs(r.rttMs)
34878
+ },
34879
+ {
34880
+ key: "url",
34881
+ header: "Event url",
34882
+ nowrap: false,
34883
+ render: (r) => /* @__PURE__ */ jsx("span", {
34884
+ className: "break-all font-mono text-[10px]",
34885
+ children: fmtText(r.eventUrl)
34886
+ })
34887
+ },
34888
+ {
34889
+ key: "note",
34890
+ header: "Note",
34891
+ nowrap: false,
34892
+ render: (r) => /* @__PURE__ */ jsx("span", {
34893
+ className: TEXT_HINT,
34894
+ children: r.error ?? (r.timedOut ? "gathering timed out" : "")
34895
+ })
34896
+ }
34897
+ ];
34898
+ var NAT_LABEL = {
34899
+ "endpoint-independent": "Endpoint-independent mapping",
34900
+ "endpoint-dependent": "Symmetric NAT (endpoint-dependent)",
34901
+ unknown: "Unknown"
34902
+ };
34903
+ function NatResult({ result }) {
34904
+ const { verdict } = result;
34905
+ return /* @__PURE__ */ jsx("div", {
34906
+ className: SECTION_BODY,
34907
+ "data-testid": "nat-check-result",
34908
+ children: /* @__PURE__ */ jsx(KeyValueList, { items: [
34909
+ {
34910
+ key: "NAT mapping",
34911
+ value: /* @__PURE__ */ jsxs("span", {
34912
+ className: "flex flex-wrap items-center gap-1.5",
34913
+ children: [/* @__PURE__ */ jsx(Badge, {
34914
+ variant: verdict.kind === "endpoint-independent" ? "success" : verdict.kind === "endpoint-dependent" ? "danger" : "default",
34915
+ children: NAT_LABEL[verdict.kind]
34916
+ }), verdict.confidence !== "none" && /* @__PURE__ */ jsx("span", {
34917
+ className: "text-[10px] text-foreground-subtle",
34918
+ children: verdict.confidence
34919
+ })]
34920
+ })
34921
+ },
34922
+ {
34923
+ key: "Why",
34924
+ value: /* @__PURE__ */ jsx("span", {
34925
+ className: "text-xs",
34926
+ children: verdict.reason
34927
+ })
34928
+ },
34929
+ {
34930
+ key: "Mappings seen",
34931
+ value: /* @__PURE__ */ jsx("span", {
34932
+ className: "font-mono text-[11px]",
34933
+ children: verdict.mappings.length > 0 ? verdict.mappings.join(", ") : "none"
34934
+ })
34935
+ },
34936
+ {
34937
+ key: "STUN queried",
34938
+ value: /* @__PURE__ */ jsx("span", {
34939
+ className: "break-all font-mono text-[11px]",
34940
+ children: result.destinations.join(", ")
34941
+ })
34942
+ },
34943
+ ...result.error !== null ? [{
34944
+ key: "Error",
34945
+ value: result.error
34946
+ }] : []
34947
+ ] })
34948
+ });
34949
+ }
34950
+ /** The per-endpoint result table — one row per tested ICE URL. */
34951
+ function EndpointResultsCard({ rows }) {
34952
+ return /* @__PURE__ */ jsx(SectionCard, {
34953
+ headingId: "webrtc-diag-endpoint-results",
34954
+ title: "Per-endpoint results",
34955
+ icon: Play,
34956
+ children: /* @__PURE__ */ jsx(DataTable, {
34957
+ columns: RESULT_COLUMNS,
34958
+ rows,
34959
+ rowKey: (r) => r.id,
34960
+ bordered: false,
34961
+ minWidthPx: 760,
34962
+ mobileMode: "cards"
34963
+ })
34964
+ });
34965
+ }
34966
+ /** The NAT mapping verdict for this browser. */
34967
+ function NatResultCard({ result }) {
34968
+ return /* @__PURE__ */ jsx(SectionCard, {
34969
+ headingId: "webrtc-diag-nat",
34970
+ title: "NAT mapping (this browser)",
34971
+ icon: Route,
34972
+ children: /* @__PURE__ */ jsx(NatResult, { result })
34973
+ });
34974
+ }
34975
+ function EndpointRow({ endpoint, checked, onToggle }) {
34976
+ return /* @__PURE__ */ jsxs("label", {
34977
+ className: `${LIST_ROW} flex cursor-pointer items-center gap-2 hover:bg-surface-hover/40`,
34978
+ children: [
34979
+ /* @__PURE__ */ jsx(Checkbox, {
34980
+ checked,
34981
+ onChange: () => onToggle(endpoint.id)
34982
+ }),
34983
+ /* @__PURE__ */ jsx(Badge, {
34984
+ variant: SCHEME_VARIANT[endpoint.scheme],
34985
+ className: "shrink-0 uppercase text-[10px]",
34986
+ children: endpoint.scheme
34987
+ }),
34988
+ /* @__PURE__ */ jsx("span", {
34989
+ className: "min-w-0 flex-1 break-all font-mono text-[11px] text-foreground",
34990
+ children: endpoint.url
34991
+ }),
34992
+ endpoint.transport !== null && /* @__PURE__ */ jsx("span", {
34993
+ className: `text-[10px] text-foreground-subtle uppercase`,
34994
+ children: endpoint.transport
34995
+ }),
34996
+ endpoint.hasCredentials && /* @__PURE__ */ jsx("span", {
34997
+ className: `text-[10px] text-foreground-subtle text-success`,
34998
+ children: "auth"
34999
+ })
35000
+ ]
35001
+ });
35002
+ }
35003
+ function EndpointTestCard({ loadServers }) {
35004
+ const [endpoints, setEndpoints] = useState(null);
35005
+ const [loadError, setLoadError] = useState(null);
35006
+ const [loading, setLoading] = useState(false);
35007
+ const [enabled, setEnabled] = useState(/* @__PURE__ */ new Set());
35008
+ const [running, setRunning] = useState(null);
35009
+ const [rows, setRows] = useState([]);
35010
+ const [nat, setNat] = useState(null);
35011
+ const reload = useCallback(async () => {
35012
+ setLoading(true);
35013
+ setLoadError(null);
35014
+ try {
35015
+ const flat = flattenIceServers(await loadServers());
35016
+ setEndpoints(flat);
35017
+ setEnabled(new Set(flat.map((e) => e.id)));
35018
+ } catch (err) {
35019
+ setLoadError(errorText(err));
35020
+ } finally {
35021
+ setLoading(false);
35022
+ }
35023
+ }, [loadServers]);
35024
+ useEffect(() => {
35025
+ reload();
35026
+ }, [reload]);
35027
+ const selected = useMemo(() => (endpoints ?? []).filter((e) => enabled.has(e.id)), [endpoints, enabled]);
35028
+ const toggle = useCallback((id) => {
35029
+ setEnabled((prev) => {
35030
+ const next = new Set(prev);
35031
+ if (next.has(id)) next.delete(id);
35032
+ else next.add(id);
35033
+ return next;
35034
+ });
35035
+ }, []);
35036
+ const runEndpoints = useCallback(async () => {
35037
+ setRunning("endpoints");
35038
+ setRows([]);
35039
+ const out = [];
35040
+ for (const e of selected) {
35041
+ out.push(await testEndpoint(e));
35042
+ setRows([...out]);
35043
+ }
35044
+ setRunning(null);
35045
+ }, [selected]);
35046
+ const runNat = useCallback(async () => {
35047
+ setRunning("nat");
35048
+ setNat(null);
35049
+ try {
35050
+ setNat(await runNatCheck(endpoints ?? []));
35051
+ } finally {
35052
+ setRunning(null);
35053
+ }
35054
+ }, [endpoints]);
35055
+ const busy = running !== null;
35056
+ return /* @__PURE__ */ jsxs("div", {
35057
+ className: "space-y-3 sm:space-y-4",
35058
+ children: [
35059
+ /* @__PURE__ */ jsx(SectionCard, {
35060
+ headingId: "webrtc-diag-ice-servers",
35061
+ title: `ICE servers${endpoints !== null ? ` (${String(endpoints.length)})` : ""}`,
35062
+ icon: Network,
35063
+ testId: "webrtc-diag-ice-servers",
35064
+ actions: /* @__PURE__ */ jsxs("div", {
35065
+ className: "flex flex-wrap items-center gap-1.5",
35066
+ children: [
35067
+ /* @__PURE__ */ jsxs(Button, {
35068
+ variant: "ghost",
35069
+ onClick: () => void reload(),
35070
+ disabled: loading || busy,
35071
+ children: [loading ? /* @__PURE__ */ jsx(LoaderCircle, { className: "h-3 w-3 animate-spin" }) : /* @__PURE__ */ jsx(RefreshCw, { className: "h-3 w-3" }), "Reload"]
35072
+ }),
35073
+ /* @__PURE__ */ jsxs(Button, {
35074
+ variant: "outline",
35075
+ onClick: () => void runNat(),
35076
+ disabled: busy || endpoints === null,
35077
+ children: [running === "nat" ? /* @__PURE__ */ jsx(LoaderCircle, { className: "h-3 w-3 animate-spin" }) : /* @__PURE__ */ jsx(Route, { className: "h-3 w-3" }), "Check NAT"]
35078
+ }),
35079
+ /* @__PURE__ */ jsxs(Button, {
35080
+ onClick: () => void runEndpoints(),
35081
+ disabled: busy || selected.length === 0,
35082
+ children: [running === "endpoints" ? /* @__PURE__ */ jsx(LoaderCircle, { className: "h-3 w-3 animate-spin" }) : /* @__PURE__ */ jsx(Play, { className: "h-3 w-3" }), "Test each endpoint"]
35083
+ })
35084
+ ]
35085
+ }),
35086
+ children: /* @__PURE__ */ jsxs("div", {
35087
+ className: "divide-y divide-border/50",
35088
+ children: [
35089
+ loadError !== null && /* @__PURE__ */ jsxs("div", {
35090
+ className: `px-3 sm:px-4 py-1.5 sm:py-2 text-xs text-danger`,
35091
+ children: ["Failed to load ICE servers: ", loadError]
35092
+ }),
35093
+ loadError === null && endpoints === null && /* @__PURE__ */ jsx("div", {
35094
+ className: `px-3 sm:px-4 py-1.5 sm:py-2 text-[10px] text-foreground-subtle`,
35095
+ children: "Loading…"
35096
+ }),
35097
+ endpoints !== null && endpoints.length === 0 && /* @__PURE__ */ jsx("div", {
35098
+ className: `px-3 sm:px-4 py-1.5 sm:py-2 text-[10px] text-foreground-subtle`,
35099
+ children: "No ICE servers configured. Add a TURN provider under TURN Servers."
35100
+ }),
35101
+ (endpoints ?? []).map((e) => /* @__PURE__ */ jsx(EndpointRow, {
35102
+ endpoint: e,
35103
+ checked: enabled.has(e.id),
35104
+ onToggle: toggle
35105
+ }, e.id))
35106
+ ]
35107
+ })
35108
+ }),
35109
+ rows.length > 0 && /* @__PURE__ */ jsx(EndpointResultsCard, { rows }),
35110
+ nat !== null && /* @__PURE__ */ jsx(NatResultCard, { result: nat })
35111
+ ]
35112
+ });
35113
+ }
35114
+ //#endregion
35115
+ //#region src/hooks/turn-server-cache.ts
35116
+ /** Short enough that rotated credentials are picked up promptly. */
35117
+ var DEFAULT_FRESH_TTL_MS = 5 * 6e4;
35118
+ /** Half of the shortest provider credential lifetime in the fleet (24 h). */
35119
+ var DEFAULT_STALE_CAP_MS = 720 * 6e4;
35120
+ var DEFAULT_STORAGE_KEY = "camstack:turn-servers:v1";
35121
+ function defaultStorage() {
35122
+ const g = globalThis;
35123
+ try {
35124
+ return g.localStorage ?? null;
35125
+ } catch {
35126
+ return null;
35127
+ }
35128
+ }
35129
+ /** Type guard for a persisted entry — storage content is external data. */
35130
+ function isCacheEntry(value) {
35131
+ if (typeof value !== "object" || value === null) return false;
35132
+ const v = value;
35133
+ if (typeof v.fetchedAt !== "number" || !Array.isArray(v.servers)) return false;
35134
+ return v.servers.every((s) => {
35135
+ if (typeof s !== "object" || s === null) return false;
35136
+ const srv = s;
35137
+ if (!(typeof srv.urls === "string" || Array.isArray(srv.urls) && srv.urls.every((u) => typeof u === "string"))) return false;
35138
+ if (srv.username !== void 0 && typeof srv.username !== "string") return false;
35139
+ if (srv.credential !== void 0 && typeof srv.credential !== "string") return false;
35140
+ return true;
35141
+ });
35142
+ }
35143
+ var TurnServerCache = class {
35144
+ freshTtlMs;
35145
+ staleCapMs;
35146
+ storage;
35147
+ now;
35148
+ storageKey;
35149
+ /** Keyed on the caller's tRPC client object (stable per connected system). */
35150
+ memory = /* @__PURE__ */ new WeakMap();
35151
+ /** In-flight fetch per key — concurrent callers share one round trip. */
35152
+ inflight = /* @__PURE__ */ new WeakMap();
35153
+ constructor(options = {}) {
35154
+ this.freshTtlMs = options.freshTtlMs ?? DEFAULT_FRESH_TTL_MS;
35155
+ this.staleCapMs = options.staleCapMs ?? DEFAULT_STALE_CAP_MS;
35156
+ this.storage = "storage" in options ? options.storage ?? null : defaultStorage();
35157
+ this.now = options.now ?? (() => Date.now());
35158
+ this.storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;
35159
+ }
35160
+ /**
35161
+ * Resolve the ICE servers for `key`, fetching via `fetch` only when no
35162
+ * fresh-enough entry exists. Never rejects: a failed fetch resolves to the
35163
+ * stale entry when one exists, else `undefined`.
35164
+ */
35165
+ async getOrFetch(key, fetch) {
35166
+ const at = this.now();
35167
+ const entry = this.memory.get(key) ?? this.readStorage();
35168
+ if (entry) {
35169
+ const age = at - entry.fetchedAt;
35170
+ if (age < this.freshTtlMs) return entry.servers;
35171
+ if (age < this.staleCapMs) {
35172
+ this.startFetch(key, fetch, entry);
35173
+ return entry.servers;
35174
+ }
35175
+ }
35176
+ return this.startFetch(key, fetch, entry);
35177
+ }
35178
+ startFetch(key, fetch, stale) {
35179
+ const pending = this.inflight.get(key);
35180
+ if (pending) return pending;
35181
+ const run = (async () => {
35182
+ try {
35183
+ const servers = await fetch();
35184
+ if (servers.length > 0) {
35185
+ const entry = {
35186
+ servers,
35187
+ fetchedAt: this.now()
35188
+ };
35189
+ this.memory.set(key, entry);
35190
+ this.writeStorage(entry);
35191
+ }
35192
+ return servers;
35193
+ } catch {
35194
+ return stale?.servers;
35195
+ }
35196
+ })().finally(() => {
35197
+ this.inflight.delete(key);
35198
+ });
35199
+ this.inflight.set(key, run);
35200
+ return run;
35201
+ }
35202
+ readStorage() {
35203
+ if (!this.storage) return void 0;
35204
+ try {
35205
+ const raw = this.storage.getItem(this.storageKey);
35206
+ if (raw === null) return void 0;
35207
+ const parsed = JSON.parse(raw);
35208
+ return isCacheEntry(parsed) ? parsed : void 0;
35209
+ } catch {
35210
+ return;
35211
+ }
35212
+ }
35213
+ writeStorage(entry) {
35214
+ if (!this.storage) return;
35215
+ try {
35216
+ this.storage.setItem(this.storageKey, JSON.stringify(entry));
35217
+ } catch {}
35218
+ }
35219
+ };
35220
+ //#endregion
35221
+ //#region src/hooks/use-device-webrtc.ts
35222
+ /**
35223
+ * useDeviceWebrtc — WebRTC signaling hook for device-scoped streaming.
35224
+ *
35225
+ * Provides `createSession`, `sendAnswer`, `closeSession` callbacks and
35226
+ * a `streams` list (Adaptive + High/Mid/Low) for a given device.
35227
+ *
35228
+ * Works in both admin-ui (BackendClient.trpc) and addon pages
35229
+ * (AddonPageProps.trpc) — parametrized by a minimal tRPC proxy shape.
35230
+ */
35231
+ /**
35232
+ * Hook that provides WebRTC signaling callbacks and stream choices for a device.
35233
+ *
35234
+ * @param trpc - tRPC proxy (admin-ui BackendClient.trpc or addon page trpc)
35235
+ * @param deviceId - numeric device ID (null = disabled)
35236
+ * @param pollIntervalMs - how often to refresh profile slots (default: 5000)
35237
+ */
35238
+ /** Module-level TURN/STUN credential cache. Keyed on the caller's trpc client
35239
+ * object (stable per connected system — NOT on `trpc.turnProvider`, which is
35240
+ * a proxy minted fresh on every property access), so two admin tabs pointed
35241
+ * at different hubs never cross-serve credentials from the memory layer; the
35242
+ * storage layer is per-origin, and the page's origin IS the hub. Coalesces
35243
+ * concurrent fetches (the mount prefetch and the connect's own call used to
35244
+ * race into TWO round trips) and persists across page loads so a fresh embed
35245
+ * page — one per camera open on native — finds warm credentials instead of
35246
+ * re-paying the measured 0.2–3.6 s serialized fetch. See turn-server-cache.ts. */
35247
+ var turnServersCache = new TurnServerCache();
35248
+ function useDeviceWebrtc(trpc, deviceId, pollIntervalMs = 5e3) {
35249
+ const [remoteStreams, setRemoteStreams] = useState([]);
35250
+ useEffect(() => {
35251
+ if (deviceId === null) return;
35252
+ let cancelled = false;
35253
+ const fetch = () => {
35254
+ trpc.webrtcSession.listStreams.query({ deviceId }).then((rows) => {
35255
+ if (!cancelled) setRemoteStreams(rows);
35256
+ }).catch(() => {});
35257
+ };
35258
+ fetch();
35259
+ const iv = setInterval(fetch, pollIntervalMs);
35260
+ return () => {
35261
+ cancelled = true;
35262
+ clearInterval(iv);
35263
+ };
35264
+ }, [
35265
+ trpc,
35266
+ deviceId,
35267
+ pollIntervalMs
35268
+ ]);
35269
+ const [pipelineMetrics, setPipelineMetrics] = useState(null);
35270
+ useEffect(() => {
35271
+ if (deviceId === null) return;
35272
+ let cancelled = false;
35273
+ if (trpc.pipelineOrchestrator) trpc.pipelineOrchestrator.getCameraMetrics.query({ deviceId }).then((m) => {
35274
+ if (cancelled || !m) return;
35275
+ setPipelineMetrics({
35276
+ phase: m.phase,
35277
+ detectionFps: m.actualFps,
35278
+ avgInferenceMs: m.avgInferenceTimeMs,
35279
+ droppedFrames: m.droppedFrames
35280
+ });
35281
+ }).catch(() => {});
35282
+ return () => {
35283
+ cancelled = true;
35284
+ };
35285
+ }, [trpc, deviceId]);
35286
+ useLiveEvent("pipeline.camera-metrics-snapshot", (evt) => {
35287
+ if (deviceId === null) return;
35288
+ const payload = evt.data;
35289
+ if (payload.deviceId !== deviceId || !payload.metrics) return;
35290
+ setPipelineMetrics({
35291
+ phase: payload.metrics.phase,
35292
+ detectionFps: payload.metrics.actualFps,
35293
+ avgInferenceMs: payload.metrics.avgInferenceTimeMs,
35294
+ droppedFrames: payload.metrics.droppedFrames
35295
+ });
35296
+ });
35297
+ const { streams, hasWebrtc } = useMemo(() => {
35298
+ if (deviceId === null || remoteStreams.length === 0) return {
35299
+ streams: [],
35300
+ hasWebrtc: false
35301
+ };
35302
+ return {
35303
+ streams: remoteStreams.map((r) => ({
35304
+ id: r.id,
35305
+ label: r.label,
35306
+ target: r.target,
35307
+ status: r.status,
35308
+ inputFps: r.inputFps,
35309
+ decodeFps: r.decodeFps,
35310
+ bitrateKbps: r.bitrateKbps
35311
+ })),
35312
+ hasWebrtc: true
35313
+ };
35314
+ }, [deviceId, remoteStreams]);
35315
+ const getIceServers = useCallback(async () => {
35316
+ const provider = trpc.turnProvider;
35317
+ if (!provider) return void 0;
35318
+ return turnServersCache.getOrFetch(trpc, async () => {
35319
+ return (await provider.getTurnServers.query()).map((s) => {
35320
+ return {
35321
+ urls: typeof s.urls === "string" ? s.urls : [...s.urls],
35322
+ ...s.username !== void 0 ? { username: s.username } : {},
35323
+ ...s.credential !== void 0 ? { credential: s.credential } : {}
35324
+ };
35325
+ });
35326
+ });
35327
+ }, [trpc]);
35328
+ useEffect(() => {
35329
+ getIceServers();
35330
+ }, [getIceServers]);
35331
+ const createSession = useCallback(async (target, hints) => {
35332
+ if (deviceId === null) throw new Error("No device selected");
35333
+ const res = await trpc.webrtcSession.createSession.mutate({
35334
+ deviceId,
35335
+ target,
35336
+ hints
35337
+ });
35338
+ const iceServers = await getIceServers();
35339
+ return {
35340
+ sessionId: res.sessionId,
35341
+ sdpOffer: res.sdpOffer,
35342
+ ...iceServers ? { iceServers } : {}
35343
+ };
35344
+ }, [
35345
+ trpc,
35346
+ deviceId,
35347
+ getIceServers
35348
+ ]);
35349
+ const handleOffer = useCallback(async (target, sdpOffer, sessionId, hints) => {
35350
+ if (deviceId === null) throw new Error("No device selected");
35351
+ return trpc.webrtcSession.handleOffer.mutate({
35352
+ deviceId,
35353
+ target,
35354
+ sdpOffer,
35355
+ ...sessionId !== void 0 ? { sessionId } : {},
35356
+ ...hints !== void 0 ? { hints } : {}
35357
+ });
35358
+ }, [trpc, deviceId]);
35359
+ const sendAnswer = useCallback(async (sessionId, sdpAnswer) => {
35360
+ if (deviceId === null) throw new Error("No device selected");
35361
+ await trpc.webrtcSession.handleAnswer.mutate({
35362
+ deviceId,
35363
+ sessionId,
35364
+ sdpAnswer
35365
+ });
35366
+ }, [trpc, deviceId]);
35367
+ return {
35368
+ streams,
35369
+ hasWebrtc,
35370
+ pipelineMetrics,
35371
+ createSession,
35372
+ handleOffer,
35373
+ getIceServers,
35374
+ addIceCandidate: useCallback(async (sessionId, candidate) => {
35375
+ if (deviceId === null) return;
35376
+ await trpc.webrtcSession.addIceCandidate.mutate({
35377
+ deviceId,
35378
+ sessionId,
35379
+ candidate: candidate.candidate ?? "",
35380
+ sdpMid: candidate.sdpMid ?? null,
35381
+ sdpMLineIndex: candidate.sdpMLineIndex ?? null
35382
+ }).catch(() => {});
35383
+ }, [trpc, deviceId]),
35384
+ getIceCandidates: useCallback(async (sessionId) => {
35385
+ if (deviceId === null) return {
35386
+ candidates: [],
35387
+ done: true
35388
+ };
35389
+ return trpc.webrtcSession.getIceCandidates.query({
35390
+ deviceId,
35391
+ sessionId
35392
+ });
35393
+ }, [trpc, deviceId]),
35394
+ sendAnswer,
35395
+ closeSession: useCallback(async (sessionId) => {
35396
+ if (deviceId === null) return;
35397
+ await trpc.webrtcSession.closeSession.mutate({
35398
+ deviceId,
35399
+ sessionId
35400
+ }).catch(() => {});
35401
+ }, [trpc, deviceId]),
35402
+ getSessionState: useCallback(async (sessionId) => {
35403
+ if (deviceId === null) return { pendingRenegotiation: null };
35404
+ try {
35405
+ return await trpc.webrtcSession.getSessionState.query({
35406
+ deviceId,
35407
+ sessionId
35408
+ });
35409
+ } catch {
35410
+ return { pendingRenegotiation: null };
35411
+ }
35412
+ }, [trpc, deviceId]),
35413
+ getSessionDebug: useCallback(async (sessionId) => {
35414
+ if (deviceId === null) throw new Error("getSessionDebug: no device bound to this player");
35415
+ return trpc.webrtcSession.getSessionDebug.query({
35416
+ deviceId,
35417
+ sessionId
35418
+ });
35419
+ }, [trpc, deviceId]),
35420
+ applyDownlinkProbe: useCallback(async (sessionId, downlinkMbps) => {
35421
+ const mutate = trpc.webrtcSession.applyDownlinkProbe;
35422
+ if (deviceId === null || mutate === void 0) throw new Error("applyDownlinkProbe: not available for this player");
35423
+ return mutate.mutate({
35424
+ deviceId,
35425
+ sessionId,
35426
+ downlinkMbps
35427
+ });
35428
+ }, [trpc, deviceId])
35429
+ };
35430
+ }
35431
+ //#endregion
35432
+ //#region src/composites/webrtc-diagnostics/RunReport.tsx
35433
+ var VERDICT_VARIANT = {
35434
+ healthy: "success",
35435
+ "loss-keyframe": "warning",
35436
+ transport: "danger",
35437
+ "hub-dtls": "danger",
35438
+ "media-black-hole": "danger",
35439
+ "hub-not-sending": "danger",
35440
+ unknown: "default"
35441
+ };
35442
+ var FLAG_TEXT = {
35443
+ "public-path": "has a public path (srflx / relay / public host)",
35444
+ "private-only": "LAN/Tailscale only, no srflx/relay",
35445
+ none: "answered with no candidate",
35446
+ unknown: "no answer received"
35447
+ };
35448
+ function candidateText(c) {
35449
+ const where = c.address === null ? "unknown" : `${c.address}:${c.port === null ? "?" : String(c.port)}`;
35450
+ return `${c.type} ${c.protocol ?? "?"} ${where} · ${c.scope}`;
35451
+ }
35452
+ function typeCounts(counts) {
35453
+ const parts = Object.entries(counts).filter(([, n]) => n > 0).map(([t, n]) => `${String(n)} ${t}`);
35454
+ return parts.length > 0 ? parts.join(" · ") : "none";
35455
+ }
35456
+ function Group({ title, children }) {
35457
+ return /* @__PURE__ */ jsxs("div", {
35458
+ className: "space-y-0.5",
35459
+ children: [/* @__PURE__ */ jsx("div", {
35460
+ className: TEXT_SECTION_LABEL,
35461
+ children: title
35462
+ }), children]
35463
+ });
35464
+ }
35465
+ function RunReport({ report, title }) {
35466
+ const { hub, timeline, path, media, verdict, local } = report;
35467
+ return /* @__PURE__ */ jsxs("section", {
35468
+ className: SECTION_CARD,
35469
+ "data-testid": `webrtc-diag-run-${report.policy}`,
35470
+ children: [/* @__PURE__ */ jsxs("header", {
35471
+ className: `${SECTION_HEADER} flex flex-wrap items-center gap-2`,
35472
+ children: [/* @__PURE__ */ jsx("h3", {
35473
+ className: `${TEXT_SECTION_LABEL} min-w-0 flex-1`,
35474
+ children: title
35475
+ }), /* @__PURE__ */ jsx(Badge, {
35476
+ variant: VERDICT_VARIANT[verdict.layer],
35477
+ children: verdict.title
35478
+ })]
35479
+ }), /* @__PURE__ */ jsxs("div", {
35480
+ className: `${SECTION_BODY} space-y-3`,
35481
+ children: [
35482
+ /* @__PURE__ */ jsx("p", {
35483
+ className: "text-xs text-foreground",
35484
+ children: verdict.detail
35485
+ }),
35486
+ /* @__PURE__ */ jsxs(Group, {
35487
+ title: "Hub candidates",
35488
+ children: [/* @__PURE__ */ jsx(KeyValueList, { items: [{
35489
+ key: "Offered",
35490
+ value: /* @__PURE__ */ jsxs("span", {
35491
+ className: "flex flex-wrap items-center gap-1.5 text-xs",
35492
+ children: [typeCounts(hub.countByType), /* @__PURE__ */ jsx(Badge, {
35493
+ variant: hub.flag === "private-only" ? "warning" : hub.flag === "public-path" ? "success" : "default",
35494
+ children: FLAG_TEXT[hub.flag]
35495
+ })]
35496
+ })
35497
+ }, {
35498
+ key: "Source",
35499
+ value: `${String(hub.fromSdp)} in the answer SDP · ${String(hub.fromTrickle)} trickled${hub.trickleDone === true ? " · gathering done" : ""}`
35500
+ }] }), hub.candidates.length > 0 && /* @__PURE__ */ jsx("ul", {
35501
+ className: "mt-1 space-y-0.5 font-mono text-[10px] text-foreground-subtle",
35502
+ children: hub.candidates.map((c) => /* @__PURE__ */ jsx("li", {
35503
+ className: "break-all",
35504
+ children: candidateText(c)
35505
+ }, c.raw))
35506
+ })]
35507
+ }),
35508
+ /* @__PURE__ */ jsx(Group, {
35509
+ title: "Timeline",
35510
+ children: /* @__PURE__ */ jsx(KeyValueList, { items: [
35511
+ {
35512
+ key: "ICE connected",
35513
+ value: fmtMs(timeline.iceConnectedMs)
35514
+ },
35515
+ {
35516
+ key: "DTLS connected",
35517
+ value: fmtMs(timeline.dtlsConnectedMs)
35518
+ },
35519
+ {
35520
+ key: "First frame",
35521
+ value: fmtMs(timeline.firstFrameMs)
35522
+ },
35523
+ {
35524
+ key: "ICE · DTLS final",
35525
+ value: `${fmtText(timeline.iceFinalState)} · ${fmtText(timeline.dtlsFinalState)}`
35526
+ }
35527
+ ] })
35528
+ }),
35529
+ /* @__PURE__ */ jsx(Group, {
35530
+ title: "Path",
35531
+ children: /* @__PURE__ */ jsx(KeyValueList, { items: [
35532
+ {
35533
+ key: "Selected pair",
35534
+ value: path.pairType === null ? fmtText(null) : `${path.pairType} (local/remote)`
35535
+ },
35536
+ {
35537
+ key: "Protocol · RTT",
35538
+ value: `${fmtText(path.protocol)} · ${fmtMs(path.rttMs)}`
35539
+ },
35540
+ {
35541
+ key: "Browser offered",
35542
+ value: `${typeCounts(local.countByType)}${local.relayProtocols.length > 0 ? ` · relay over ${local.relayProtocols.join("/")}` : ""}`
35543
+ }
35544
+ ] })
35545
+ }),
35546
+ /* @__PURE__ */ jsx(Group, {
35547
+ title: "Media",
35548
+ children: /* @__PURE__ */ jsx(KeyValueList, { items: [
35549
+ {
35550
+ key: "Hub sent",
35551
+ value: `${fmtCount(media.serverPacketsSent)} RTP packets${media.servedTier !== null ? ` · ${media.servedTier}` : ""}${media.egressLeg !== null ? ` · ${media.egressLeg}` : ""}`
35552
+ },
35553
+ {
35554
+ key: "Browser got",
35555
+ value: `${fmtCount(media.clientPacketsReceived)} packets · ${fmtBytes(media.clientBytesReceived)}`
35556
+ },
35557
+ {
35558
+ key: "Frames decoded",
35559
+ value: fmtCount(media.framesDecoded)
35560
+ },
35561
+ {
35562
+ key: "Loss · freezes",
35563
+ value: `${fmtPercent(media.lossPercent)} · ${fmtCount(media.freezeCount)} (${fmtMs(media.totalFreezeMs)})`
35564
+ },
35565
+ {
35566
+ key: "PLI · NACK",
35567
+ value: `${fmtCount(media.pliCount)} · ${fmtCount(media.nackCount)}`
35568
+ }
35569
+ ] })
35570
+ }),
35571
+ (report.reconnects > 0 || report.errors.length > 0 || report.serverError !== null) && /* @__PURE__ */ jsxs("div", {
35572
+ className: "text-[10px] text-foreground-subtle",
35573
+ children: [
35574
+ report.reconnects > 0 && /* @__PURE__ */ jsxs("div", { children: ["Player reconnects: ", String(report.reconnects)] }),
35575
+ report.errors.map((e, i) => /* @__PURE__ */ jsxs("div", { children: ["Player error: ", e] }, `${String(i)}-${e}`)),
35576
+ report.serverError !== null && /* @__PURE__ */ jsxs("div", { children: ["Server debug: ", report.serverError] })
35577
+ ]
35578
+ }),
35579
+ /* @__PURE__ */ jsxs("div", {
35580
+ className: TEXT_HINT,
35581
+ children: [
35582
+ "Sessions:",
35583
+ " ",
35584
+ report.sessionIds.length > 0 ? report.sessionIds.map((s) => s.slice(0, 8)).join(", ") : "none",
35585
+ " ",
35586
+ "· ",
35587
+ String(Math.round(report.durationMs / 1e3)),
35588
+ " s"
35589
+ ]
35590
+ })
35591
+ ]
35592
+ })]
35593
+ });
35594
+ }
35595
+ function transportDetail(input) {
35596
+ const state = input.iceFinalState ?? "unknown";
35597
+ if (input.hubFlag === "unknown") return `ICE ${state}; the hub never answered the offer, so no hub candidate was seen`;
35598
+ if (input.hubFlag === "none") return `ICE ${state}; the hub answered but offered no candidate`;
35599
+ if (input.policy === "relay" && input.hubFlag === "private-only") return `ICE ${state}; this browser was limited to TURN relay candidates and the hub offered only LAN/Tailscale addresses (no srflx/relay) — a relayed client cannot reach it`;
35600
+ if (input.hubFlag === "private-only") return `ICE ${state}; the hub offered only LAN/Tailscale addresses (no srflx/relay) — reachable only from inside those networks`;
35601
+ return `ICE ${state}; no candidate pair between this browser and the hub succeeded`;
35602
+ }
35603
+ function verdict(layer, title, detail) {
35604
+ return {
35605
+ layer,
35606
+ title,
35607
+ detail
35608
+ };
35609
+ }
35610
+ function mediaVerdict(input) {
35611
+ const client = input.clientPacketsReceived;
35612
+ const sent = input.serverPacketsSent;
35613
+ if (client === null) return verdict("unknown", "Media unknown", "the browser reported no inbound video statistics");
35614
+ if (client === 0) {
35615
+ if (sent === null) return verdict("unknown", "Nothing received", "the browser received no packets and the hub's own count is unknown");
35616
+ if (sent > 0) return verdict("media-black-hole", "Media black-hole", `the hub sent ${String(sent)} RTP packets and this browser received none — media is lost between them after DTLS`);
35617
+ return verdict("hub-not-sending", "Hub not sending", "the session is connected but the hub sent no RTP — the source or the broker is not producing");
35618
+ }
35619
+ if (input.framesDecoded === null) return verdict("unknown", "Decode unknown", "packets arrive; the browser reported no decode count");
35620
+ if (input.framesDecoded === 0) return verdict("loss-keyframe", "Loss / keyframe size", "packets arrive but no frame decoded — a keyframe never completes over this path");
35621
+ const { pliCount: pli, freezeCount: freezes, lossPercent: loss } = input;
35622
+ const storm = pli !== null && pli >= 3 || freezes !== null && freezes > 0 || loss !== null && loss >= 2;
35623
+ const signals = [
35624
+ `${pli === null ? "unknown" : String(pli)} PLI`,
35625
+ `${freezes === null ? "unknown" : String(freezes)} freezes`,
35626
+ `${loss === null ? "unknown" : `${loss.toFixed(1)}%`} loss`
35627
+ ].join(", ");
35628
+ if (storm) return verdict("loss-keyframe", "Loss / keyframe size", `frames decode but the path is lossy: ${signals}`);
35629
+ if (pli === null && freezes === null && loss === null) return verdict("healthy", "Decoding", "frames decode; the browser reported no loss signal to judge");
35630
+ return verdict("healthy", "Healthy", `connected, receiving and decoding: ${signals}`);
35631
+ }
35632
+ function classifySessionVerdict(input) {
35633
+ if (!input.iceConnected) return verdict("transport", "Transport (ICE) failed", transportDetail(input));
35634
+ if (!input.dtlsConnected && input.dtlsFinalState !== null) return verdict("hub-dtls", "DTLS stuck (hub side)", `ICE connected but DTLS stayed ${input.dtlsFinalState} — the hub never completed the handshake`);
35635
+ return mediaVerdict(input);
35636
+ }
35637
+ //#endregion
35638
+ //#region src/composites/webrtc-diagnostics/session-run.ts
35639
+ function emptySessionRun(policy) {
35640
+ return {
35641
+ policy,
35642
+ sessionIds: [],
35643
+ sdpAnswer: null,
35644
+ trickled: [],
35645
+ trickleDone: null,
35646
+ local: [],
35647
+ ice: [],
35648
+ dtls: [],
35649
+ peer: [],
35650
+ firstFrameAt: null,
35651
+ lastStats: null,
35652
+ lastServer: null,
35653
+ maxServerPacketsSent: null,
35654
+ lastServerError: null,
35655
+ samples: 0,
35656
+ reconnects: 0,
35657
+ errors: []
35658
+ };
35659
+ }
35660
+ function mark(list, state, at) {
35661
+ return list.at(-1)?.state === state ? list : [...list, {
35662
+ state,
35663
+ at
35664
+ }];
35665
+ }
35666
+ function maxOrNull(a, b) {
35667
+ if (a === null) return b;
35668
+ if (b === null) return a;
35669
+ return Math.max(a, b);
35670
+ }
35671
+ function reduceSample(state, sample) {
35672
+ return {
35673
+ ...state,
35674
+ samples: state.samples + 1,
35675
+ lastStats: sample.stats ?? state.lastStats,
35676
+ lastServer: sample.server ?? state.lastServer,
35677
+ maxServerPacketsSent: maxOrNull(state.maxServerPacketsSent, sample.server?.rtpPacketsSent ?? null),
35678
+ lastServerError: sample.serverError,
35679
+ sessionIds: sample.sessionId !== null && !state.sessionIds.includes(sample.sessionId) ? [...state.sessionIds, sample.sessionId] : state.sessionIds
35680
+ };
35681
+ }
35682
+ function reduceSessionRun(state, event) {
35683
+ switch (event.kind) {
35684
+ case "offer-answered": return {
35685
+ ...state,
35686
+ sdpAnswer: state.sdpAnswer ?? event.sdpAnswer,
35687
+ sessionIds: state.sessionIds.includes(event.sessionId) ? state.sessionIds : [...state.sessionIds, event.sessionId]
35688
+ };
35689
+ case "hub-candidates": return {
35690
+ ...state,
35691
+ trickled: [...state.trickled, ...event.candidates.filter((c) => !state.trickled.includes(c))],
35692
+ trickleDone: event.done || state.trickleDone === true
35693
+ };
35694
+ case "local-candidate": {
35695
+ const candidate = parseIceCandidate(event.candidate);
35696
+ if (candidate === null) return state;
35697
+ return {
35698
+ ...state,
35699
+ local: [...state.local, {
35700
+ candidate,
35701
+ url: event.url,
35702
+ relayProtocol: event.relayProtocol
35703
+ }]
35704
+ };
35705
+ }
35706
+ case "ice-state": return {
35707
+ ...state,
35708
+ ice: mark(state.ice, event.state, event.at)
35709
+ };
35710
+ case "dtls-state": return {
35711
+ ...state,
35712
+ dtls: mark(state.dtls, event.state, event.at)
35713
+ };
35714
+ case "peer-state": return {
35715
+ ...state,
35716
+ peer: mark(state.peer, event.state, event.at)
35717
+ };
35718
+ case "first-frame": return {
35719
+ ...state,
35720
+ firstFrameAt: state.firstFrameAt ?? event.at
35721
+ };
35722
+ case "sample": return reduceSample(state, event.sample);
35723
+ case "reconnect": return {
35724
+ ...state,
35725
+ reconnects: state.reconnects + 1
35726
+ };
35727
+ case "error": return {
35728
+ ...state,
35729
+ errors: [...state.errors, event.message]
35730
+ };
35731
+ }
35732
+ }
35733
+ var CONNECTED_ICE = new Set(["connected", "completed"]);
35734
+ function firstAt(list, states) {
35735
+ return list.find((m) => states.has(m.state))?.at ?? null;
35736
+ }
35737
+ var CONNECTED = new Set(["connected"]);
35738
+ function timelineOf(state) {
35739
+ const dtlsAt = firstAt(state.dtls, CONNECTED) ?? firstAt(state.peer, CONNECTED);
35740
+ const dtlsFinal = state.dtls.at(-1)?.state ?? state.peer.at(-1)?.state ?? null;
35741
+ return {
35742
+ iceConnectedMs: firstAt(state.ice, CONNECTED_ICE),
35743
+ dtlsConnectedMs: dtlsAt,
35744
+ firstFrameMs: state.firstFrameAt,
35745
+ iceFinalState: state.ice.at(-1)?.state ?? null,
35746
+ dtlsFinalState: dtlsFinal
35747
+ };
35748
+ }
35749
+ function mediaOf(state) {
35750
+ const s = state.lastStats;
35751
+ const srv = state.lastServer;
35752
+ return {
35753
+ clientBytesReceived: s?.raw.bytesReceived ?? null,
35754
+ clientPacketsReceived: s?.packetsReceived ?? null,
35755
+ framesDecoded: s?.framesDecoded ?? null,
35756
+ serverPacketsSent: state.maxServerPacketsSent,
35757
+ serverAlive: srv?.alive ?? null,
35758
+ servedTier: srv?.servedTier ?? null,
35759
+ egressLeg: srv?.egressLeg ?? null,
35760
+ lossPercent: s?.lossPercent ?? null,
35761
+ freezeCount: s?.freezeCount ?? null,
35762
+ totalFreezeMs: s?.totalFreezeMs ?? null,
35763
+ pliCount: s?.pliCount ?? null,
35764
+ nackCount: s?.nackCount ?? null
35765
+ };
35766
+ }
35767
+ function localOf(state) {
35768
+ const unique = (xs) => [...new Set(xs.filter((x) => x !== null))];
35769
+ return {
35770
+ countByType: countCandidateTypes(state.local.map((l) => l.candidate)),
35771
+ relayProtocols: unique(state.local.filter((l) => l.candidate.type === "relay").map((l) => l.relayProtocol)),
35772
+ urls: unique(state.local.map((l) => l.url))
35773
+ };
35774
+ }
35775
+ function finalizeSessionRun(state, durationMs) {
35776
+ const hub = summarizeHubCandidates({
35777
+ sdpAnswer: state.sdpAnswer,
35778
+ trickled: state.trickled,
35779
+ trickleDone: state.trickleDone
35780
+ });
35781
+ const timeline = timelineOf(state);
35782
+ const media = mediaOf(state);
35783
+ const verdict = classifySessionVerdict({
35784
+ policy: state.policy,
35785
+ durationMs,
35786
+ iceConnected: timeline.iceConnectedMs !== null,
35787
+ iceFinalState: timeline.iceFinalState,
35788
+ dtlsConnected: timeline.dtlsConnectedMs !== null,
35789
+ dtlsFinalState: timeline.dtlsFinalState,
35790
+ hubFlag: hub.flag,
35791
+ clientPacketsReceived: media.clientPacketsReceived,
35792
+ framesDecoded: media.framesDecoded,
35793
+ serverPacketsSent: media.serverPacketsSent,
35794
+ pliCount: media.pliCount,
35795
+ freezeCount: media.freezeCount,
35796
+ lossPercent: media.lossPercent
35797
+ });
35798
+ return {
35799
+ policy: state.policy,
35800
+ durationMs,
35801
+ sessionIds: state.sessionIds,
35802
+ hub,
35803
+ local: localOf(state),
35804
+ timeline,
35805
+ path: {
35806
+ pairType: state.lastStats?.candidatePairType ?? null,
35807
+ protocol: state.lastStats?.candidateProtocol ?? null,
35808
+ rttMs: state.lastStats?.rttMs ?? null
35809
+ },
35810
+ media,
35811
+ reconnects: state.reconnects,
35812
+ errors: state.errors,
35813
+ serverError: state.lastServerError,
35814
+ verdict
35815
+ };
35816
+ }
35817
+ //#endregion
35818
+ //#region src/composites/webrtc-diagnostics/HubSessionTestCard.tsx
35819
+ /**
35820
+ * "Hub session test" — a real live session from THIS browser to the hub,
35821
+ * through the same `CameraStreamPlayer` + `useDeviceWebrtc` signaling every
35822
+ * viewer uses, run twice: browser ICE policy `all`, then `relay`.
35823
+ *
35824
+ * Nothing is simulated. The player's own client-offer path builds the offer,
35825
+ * trickles, polls the hub's candidates, and reports its 1 Hz
35826
+ * `StreamDebugSample` (the `webrtc-debug-stats` collector plus the server's
35827
+ * `getSessionDebug`). This card only LISTENS: it wraps the signaling callbacks
35828
+ * to see the hub's answer SDP and trickled candidates, and adds listeners to
35829
+ * each peer connection the player builds (`onPeerConnection`) for the ICE /
35830
+ * DTLS timeline and the browser's own candidates. The target is pinned (a low
35831
+ * or sub stream by default) so a large keyframe does not decide the verdict.
35832
+ */
35833
+ var HUB_SESSION_RUN_MS = 15e3;
35834
+ var POLICIES = ["all", "relay"];
35835
+ /** Between runs, so the first session is closed on the hub before the second opens. */
35836
+ var RUN_GAP_MS = 750;
35837
+ var PROGRESS_TICK_MS = 500;
35838
+ /** Pinned targets only, cheapest first: an adaptive target would climb mid-run. */
35839
+ function pinnedStreamChoices(streams) {
35840
+ const rank = (s) => {
35841
+ const t = s.target;
35842
+ if (t.kind === "profile") return t.profile === "low" ? 0 : t.profile === "mid" ? 2 : 3;
35843
+ if (t.kind === "cam-stream") return /sub|low|ext/i.test(t.camStreamId) ? 1 : 4;
35844
+ return 9;
35845
+ };
35846
+ return streams.filter((s) => s.target.kind !== "adaptive").toSorted((a, b) => rank(a) - rank(b));
35847
+ }
35848
+ var POLICY_LABEL = {
35849
+ all: "ICE policy all",
35850
+ relay: "ICE policy relay (TURN only)"
35851
+ };
35852
+ function HubSessionTestCard({ trpc, serverUrl, deviceId, cameraField, runMs = HUB_SESSION_RUN_MS }) {
35853
+ const webrtc = useDeviceWebrtc(trpc, deviceId);
35854
+ const choices = useMemo(() => pinnedStreamChoices(webrtc.streams), [webrtc.streams]);
35855
+ const [choiceId, setChoiceId] = useState(null);
35856
+ const choice = choices.find((c) => c.id === choiceId) ?? choices[0] ?? null;
35857
+ const [live, setLive] = useState(null);
35858
+ const [reports, setReports] = useState([]);
35859
+ const [elapsedMs, setElapsedMs] = useState(0);
35860
+ /** True from Run until the last policy reported or Stop — the gap between runs included. */
35861
+ const [active, setActive] = useState(false);
35862
+ const stateRef = useRef(emptySessionRun("all"));
35863
+ const t0Ref = useRef(0);
35864
+ const tokenRef = useRef(0);
35865
+ const gapTimerRef = useRef(null);
35866
+ const elapsed = useCallback(() => Math.round(performance.now() - t0Ref.current), []);
35867
+ /** Fold one observation into the CURRENT run; a late event from a closed run is dropped. */
35868
+ const record = useCallback((token, event) => {
35869
+ if (token !== tokenRef.current) return;
35870
+ stateRef.current = reduceSessionRun(stateRef.current, event);
35871
+ }, []);
35872
+ const stop = useCallback(() => {
35873
+ tokenRef.current += 1;
35874
+ if (gapTimerRef.current !== null) clearTimeout(gapTimerRef.current);
35875
+ gapTimerRef.current = null;
35876
+ setLive(null);
35877
+ setActive(false);
35878
+ }, []);
35879
+ const startRun = useCallback((index) => {
35880
+ const policy = POLICIES[index];
35881
+ if (policy === void 0) return;
35882
+ tokenRef.current += 1;
35883
+ stateRef.current = emptySessionRun(policy);
35884
+ t0Ref.current = performance.now();
35885
+ setElapsedMs(0);
35886
+ setLive({
35887
+ index,
35888
+ policy,
35889
+ token: tokenRef.current
35890
+ });
35891
+ }, []);
35892
+ useEffect(() => {
35893
+ if (live === null) return;
35894
+ const tick = setInterval(() => setElapsedMs(elapsed()), PROGRESS_TICK_MS);
35895
+ const done = setTimeout(() => {
35896
+ const report = finalizeSessionRun(stateRef.current, elapsed());
35897
+ setReports((prev) => [...prev, report]);
35898
+ tokenRef.current += 1;
35899
+ setLive(null);
35900
+ const next = live.index + 1;
35901
+ if (next < POLICIES.length) gapTimerRef.current = setTimeout(() => startRun(next), RUN_GAP_MS);
35902
+ else setActive(false);
35903
+ }, runMs);
35904
+ return () => {
35905
+ clearInterval(tick);
35906
+ clearTimeout(done);
35907
+ };
35908
+ }, [
35909
+ live,
35910
+ runMs,
35911
+ elapsed,
35912
+ startRun
35913
+ ]);
35914
+ useEffect(() => () => {
35915
+ tokenRef.current += 1;
35916
+ if (gapTimerRef.current !== null) clearTimeout(gapTimerRef.current);
35917
+ }, []);
35918
+ const start = useCallback(() => {
35919
+ setReports([]);
35920
+ setActive(true);
35921
+ startRun(0);
35922
+ }, [startRun]);
35923
+ const target = choice?.target ?? null;
35924
+ const token = live?.token ?? -1;
35925
+ const handleOffer = useCallback(async (sdpOffer, sessionId, hints) => {
35926
+ if (target === null) throw new Error("no stream selected");
35927
+ const res = await webrtc.handleOffer(target, sdpOffer, sessionId, hints);
35928
+ record(token, {
35929
+ kind: "offer-answered",
35930
+ at: elapsed(),
35931
+ sessionId: res.sessionId,
35932
+ sdpAnswer: res.sdpAnswer
35933
+ });
35934
+ return res;
35935
+ }, [
35936
+ target,
35937
+ webrtc,
35938
+ record,
35939
+ token,
35940
+ elapsed
35941
+ ]);
35942
+ const getIceCandidates = useCallback(async (sessionId) => {
35943
+ const res = await webrtc.getIceCandidates(sessionId);
35944
+ record(token, {
35945
+ kind: "hub-candidates",
35946
+ at: elapsed(),
35947
+ candidates: res.candidates.map((c) => c.candidate ?? "").filter((c) => c.length > 0),
35948
+ done: res.done
35949
+ });
35950
+ return res;
35951
+ }, [
35952
+ webrtc,
35953
+ record,
35954
+ token,
35955
+ elapsed
35956
+ ]);
35957
+ const onPeerConnection = useCallback((pc) => {
35958
+ const seen = /* @__PURE__ */ new WeakSet();
35959
+ pc.addEventListener("iceconnectionstatechange", () => record(token, {
35960
+ kind: "ice-state",
35961
+ at: elapsed(),
35962
+ state: pc.iceConnectionState
35963
+ }));
35964
+ pc.addEventListener("connectionstatechange", () => record(token, {
35965
+ kind: "peer-state",
35966
+ at: elapsed(),
35967
+ state: pc.connectionState
35968
+ }));
35969
+ pc.addEventListener("icecandidate", (e) => {
35970
+ const probed = probedCandidateFromEvent(e, elapsed());
35971
+ if (probed === null) return;
35972
+ record(token, {
35973
+ kind: "local-candidate",
35974
+ at: probed.tMs,
35975
+ candidate: probed.candidate.raw,
35976
+ url: probed.url,
35977
+ relayProtocol: probed.relayProtocol
35978
+ });
35979
+ });
35980
+ pc.addEventListener("track", (e) => {
35981
+ const transport = e.receiver.transport;
35982
+ if (transport === null || seen.has(transport)) return;
35983
+ seen.add(transport);
35984
+ record(token, {
35985
+ kind: "dtls-state",
35986
+ at: elapsed(),
35987
+ state: transport.state
35988
+ });
35989
+ transport.addEventListener("statechange", () => record(token, {
35990
+ kind: "dtls-state",
35991
+ at: elapsed(),
35992
+ state: transport.state
35993
+ }));
35994
+ });
35995
+ }, [
35996
+ record,
35997
+ token,
35998
+ elapsed
35999
+ ]);
36000
+ const onDebugStats = useCallback((sample) => record(token, {
36001
+ kind: "sample",
36002
+ at: elapsed(),
36003
+ sample
36004
+ }), [
36005
+ record,
36006
+ token,
36007
+ elapsed
36008
+ ]);
36009
+ const onConnectTiming = useCallback((timing) => {
36010
+ if (timing["firstFrameMs"] !== void 0) record(token, {
36011
+ kind: "first-frame",
36012
+ at: elapsed()
36013
+ });
36014
+ }, [
36015
+ record,
36016
+ token,
36017
+ elapsed
36018
+ ]);
36019
+ const progress = live === null ? null : Math.min(100, Math.round(elapsedMs / runMs * 100));
36020
+ return /* @__PURE__ */ jsxs(SectionCard, {
36021
+ headingId: "webrtc-diag-hub-session",
36022
+ title: "Hub session test",
36023
+ icon: Activity,
36024
+ testId: "webrtc-diag-hub-session",
36025
+ actions: !active ? /* @__PURE__ */ jsxs(Button, {
36026
+ onClick: start,
36027
+ disabled: !(deviceId !== null && choice !== null && !active),
36028
+ children: [
36029
+ /* @__PURE__ */ jsx(Play, { className: "h-3 w-3" }),
36030
+ "Run test (",
36031
+ String(Math.round(runMs * POLICIES.length / 1e3)),
36032
+ " s)"
36033
+ ]
36034
+ }) : /* @__PURE__ */ jsxs(Button, {
36035
+ variant: "outline",
36036
+ onClick: stop,
36037
+ children: [/* @__PURE__ */ jsx(Square, { className: "h-3 w-3" }), "Stop"]
36038
+ }),
36039
+ children: [
36040
+ /* @__PURE__ */ jsxs("div", {
36041
+ className: `${SECTION_BODY} space-y-3`,
36042
+ children: [
36043
+ /* @__PURE__ */ jsxs("div", {
36044
+ className: `grid grid-cols-1 sm:grid-cols-2 ${GRID_GAP}`,
36045
+ children: [cameraField, /* @__PURE__ */ jsxs("label", {
36046
+ className: "flex min-w-0 flex-col gap-1",
36047
+ children: [/* @__PURE__ */ jsx("span", {
36048
+ className: "text-[11px] text-foreground-subtle",
36049
+ children: "Stream (pinned)"
36050
+ }), /* @__PURE__ */ jsx(Select, {
36051
+ value: choice?.id ?? "",
36052
+ disabled: choices.length === 0 || active,
36053
+ onChange: (e) => setChoiceId(e.target.value),
36054
+ options: choices.length === 0 ? [{
36055
+ value: "",
36056
+ label: deviceId === null ? "Pick a camera first" : "No pinned stream"
36057
+ }] : choices.map((c) => ({
36058
+ value: c.id,
36059
+ label: c.label
36060
+ }))
36061
+ })]
36062
+ })]
36063
+ }),
36064
+ /* @__PURE__ */ jsx("p", {
36065
+ className: TEXT_HINT,
36066
+ children: "Opens a real live session from this browser twice — first with every candidate type, then TURN relay only — and reads both ends of it."
36067
+ }),
36068
+ live !== null && deviceId !== null && choice !== null && /* @__PURE__ */ jsxs("div", {
36069
+ className: `grid grid-cols-1 sm:grid-cols-[minmax(0,16rem)_1fr] gap-2 sm:gap-3 items-start`,
36070
+ children: [/* @__PURE__ */ jsx("div", {
36071
+ className: "aspect-video w-full overflow-hidden rounded-md border border-border bg-black",
36072
+ children: /* @__PURE__ */ jsx(CameraStreamPlayer, {
36073
+ serverUrl,
36074
+ streamKey: `diag-${choice.id}-${live.policy}`,
36075
+ autoPlay: true,
36076
+ muted: true,
36077
+ showControls: false,
36078
+ className: "h-full w-full",
36079
+ handleOffer,
36080
+ getIceServers: webrtc.getIceServers,
36081
+ addIceCandidate: webrtc.addIceCandidate,
36082
+ getIceCandidates,
36083
+ closeSession: webrtc.closeSession,
36084
+ getSessionDebug: webrtc.getSessionDebug,
36085
+ applyDownlinkProbe: webrtc.applyDownlinkProbe,
36086
+ iceTransportPolicy: live.policy,
36087
+ onPeerConnection,
36088
+ onDebugStats,
36089
+ onConnectTiming,
36090
+ onReconnectAttempt: () => record(token, {
36091
+ kind: "reconnect",
36092
+ at: elapsed()
36093
+ }),
36094
+ onError: (message) => record(token, {
36095
+ kind: "error",
36096
+ at: elapsed(),
36097
+ message
36098
+ })
36099
+ }, `${String(live.token)}-${live.policy}`)
36100
+ }), /* @__PURE__ */ jsxs("div", {
36101
+ className: "space-y-1.5",
36102
+ "data-testid": "webrtc-diag-progress",
36103
+ children: [
36104
+ /* @__PURE__ */ jsxs("div", {
36105
+ className: "flex items-center gap-2 text-xs text-foreground",
36106
+ children: [
36107
+ /* @__PURE__ */ jsx(LoaderCircle, { className: "h-3 w-3 animate-spin" }),
36108
+ "Run ",
36109
+ String(live.index + 1),
36110
+ " of ",
36111
+ String(POLICIES.length),
36112
+ " ·",
36113
+ " ",
36114
+ POLICY_LABEL[live.policy]
36115
+ ]
36116
+ }),
36117
+ /* @__PURE__ */ jsx("div", {
36118
+ className: "h-1.5 w-full overflow-hidden rounded bg-surface-hover",
36119
+ children: /* @__PURE__ */ jsx("div", {
36120
+ className: "h-full bg-primary transition-[width]",
36121
+ style: { width: `${String(progress ?? 0)}%` }
36122
+ })
36123
+ }),
36124
+ /* @__PURE__ */ jsxs("div", {
36125
+ className: "text-[10px] text-foreground-subtle",
36126
+ children: [
36127
+ String(Math.round(elapsedMs / 1e3)),
36128
+ " s of ",
36129
+ String(Math.round(runMs / 1e3)),
36130
+ " s"
36131
+ ]
36132
+ })
36133
+ ]
36134
+ })]
36135
+ })
36136
+ ]
36137
+ }),
36138
+ reports.length > 0 && /* @__PURE__ */ jsx("div", {
36139
+ className: `grid grid grid-cols-1 lg:grid-cols-2 gap-2 sm:gap-3 border-t border-border p-2 sm:p-3`,
36140
+ children: reports.map((r, i) => /* @__PURE__ */ jsx(RunReport, {
36141
+ report: r,
36142
+ title: `Run ${String(i + 1)} · ${POLICY_LABEL[r.policy]}`
36143
+ }, `${String(i)}-${r.policy}`))
36144
+ }),
36145
+ reports.length === 0 && !active && /* @__PURE__ */ jsx("div", {
36146
+ className: `px-3 sm:px-4 py-1.5 sm:py-2 border-t border-border text-[10px] text-foreground-subtle`,
36147
+ children: "No run yet."
36148
+ })
36149
+ ]
36150
+ });
36151
+ }
36152
+ //#endregion
36153
+ //#region src/composites/cap-settings/scene-monitor-copy.ts
36154
+ /** The lighting variants an operator is OFFERED. The resolver can also report
36155
+ * `night`, `dawn` and `dusk`; those are shown when present but never demanded,
36156
+ * because most installs only ever see day and IR. */
36157
+ var SCENE_CAPTURE_VARIANTS = ["day", "ir"];
36158
+ var VARIANT_LABEL = {
36159
+ day: "Daylight",
36160
+ ir: "Night (IR)",
36161
+ night: "Night",
36162
+ dawn: "Dawn",
36163
+ dusk: "Dusk"
36164
+ };
36165
+ function variantLabel(condition) {
36166
+ return VARIANT_LABEL[condition] ?? condition;
36167
+ }
34228
36168
  /**
34229
- * The toggle rule, and the only copy of it.
36169
+ * "Daylight ✓ · Night (IR) ✗ — optional; checks in night (ir) are skipped."
34230
36170
  *
34231
- * none → asc → desc → none, and a click on a DIFFERENT column starts that
34232
- * column fresh at `asc`. `sameColumn` says whether the clicked column is the
34233
- * one currently sorted; `currentDir` is its direction (`null` when nothing is
34234
- * sorted). Returns the next direction, or `null` for "unsorted".
36171
+ * Converts the silent cross-condition fallback this engine used to have into an
36172
+ * operator-visible fact. Without it, a scene with only a daylight reference
36173
+ * simply stops answering after sunset and nothing anywhere says why.
34235
36174
  *
34236
- * Split out from {@link nextTableSort} so the device list — whose sort state
34237
- * keys columns by `columnId`, not `columnKey` — can reuse the rule without a
34238
- * cast and without a second copy of it.
36175
+ * It used to end *"reports 'can't tell' in night (ir)"*, which was accurate and
36176
+ * read as a defect: the operator saw a ✗ and a complaint for a scene that was
36177
+ * configured exactly as they meant it. The missing half is now named as what it
36178
+ * is — the part you do not have to do — and the sentence says what actually
36179
+ * happens instead, which differs by the scene's own policy.
34239
36180
  */
34240
- function nextSortDirection(sameColumn, currentDir) {
34241
- if (!sameColumn || currentDir === null) return "asc";
34242
- return currentDir === "asc" ? "desc" : null;
36181
+ function coverageLine(monitor) {
36182
+ const covered = new Set(monitor.coveredConditions);
36183
+ const parts = SCENE_CAPTURE_VARIANTS.map((v) => `${variantLabel(v)} ${covered.has(v) ? "✓" : "✗"}`);
36184
+ const extra = [...covered].filter((c) => !SCENE_CAPTURE_VARIANTS.some((v) => v === c));
36185
+ for (const c of extra) parts.push(`${variantLabel(c)} ✓`);
36186
+ const missing = SCENE_CAPTURE_VARIANTS.filter((v) => !covered.has(v));
36187
+ if (missing.length === 0) return parts.join(" · ");
36188
+ if (covered.size === 0) return `${parts.join(" · ")} — nothing captured yet, so this scene is not watching.`;
36189
+ const names = missing.map((v) => variantLabel(v).toLowerCase()).join(" and ");
36190
+ return monitor.onUncoveredCondition === "judge-anyway" ? `${parts.join(" · ")} — ${names} judged against the references you do have.` : `${parts.join(" · ")} — optional; checks in ${names} are skipped.`;
34243
36191
  }
34244
36192
  /**
34245
- * Toggle a column's sort on header click: none → asc → desc → none.
34246
- * Clicking a DIFFERENT column starts it fresh at `asc`.
36193
+ * The calm sentence for a scene that is sitting the current light out — `null`
36194
+ * whenever it is checking normally.
36195
+ *
36196
+ * Separate from {@link statusLine} because it is not a verdict and must not
36197
+ * read like one. A paused scene has not failed, has not lost its flag, and
36198
+ * needs nothing from the operator; the only thing worth saying is which light
36199
+ * it is waiting out and that it will pick itself back up.
34247
36200
  */
34248
- function nextTableSort(current, columnKey) {
34249
- const dir = nextSortDirection(current !== null && current.columnKey === columnKey, current === null ? null : current.dir);
34250
- return dir === null ? null : {
34251
- columnKey,
34252
- dir
34253
- };
36201
+ function suspensionLine(monitor) {
36202
+ if (monitor.suspendedCondition === null) return null;
36203
+ const light = variantLabel(monitor.suspendedCondition).toLowerCase();
36204
+ return `Paused — nothing was captured in ${light}, so checks are on hold until the light changes back. Capture ${light} if you want it watched around the clock.`;
34254
36205
  }
36206
+ var UNAVAILABLE_SENTENCE = {
36207
+ "no-reference-for-condition": "can’t tell — nothing has been captured for this scene yet. Press “Capture daylight” to photograph what normal looks like.",
36208
+ "view-shifted": "can’t tell — the camera view has moved since the reference was captured. Re-capture it.",
36209
+ "no-vision-profile": "can’t tell — the vision model is unreachable, so the change is on hold.",
36210
+ "encoder-model-changed": "can’t tell — the embedding model changed, so the stored references are unreadable. Re-capture them.",
36211
+ "no-snapshot": "can’t tell — this camera did not return a frame."
36212
+ };
34255
36213
  /**
34256
- * Ascending comparison of two cell values.
34257
- *
34258
- * Numbers compare numerically and strings compare with `localeCompare` — a
34259
- * numeric column must never be ordered as text ("100" before "9"), and a text
34260
- * column must respect the locale's collation. A mixed column (one row a
34261
- * number, another a string) falls back to string comparison of both, which is
34262
- * the only total order available; it is not a shape any caller should build on
34263
- * purpose.
34264
- *
34265
- * `null` is NOT handled here — {@link sortRowsByColumn} pulls it out first,
34266
- * because its placement is direction-independent and a comparator that is
34267
- * multiplied by the direction factor cannot express that.
36214
+ * The one line under a scene's name. It answers, in order: can it judge, what
36215
+ * does it say, and how long has it said it.
34268
36216
  */
34269
- function compareValues(a, b) {
34270
- if (typeof a === "number" && typeof b === "number") return a - b;
34271
- return String(a).localeCompare(String(b));
36217
+ function statusLine(monitor, now) {
36218
+ if (monitor.states.length === 0) return "Not armed yet — capture the initial state to start watching.";
36219
+ if (monitor.verdict === "unknown") return monitor.unavailable !== null ? UNAVAILABLE_SENTENCE[monitor.unavailable] : "can’t tell right now.";
36220
+ const since = monitor.verdict === "diverged" ? monitor.divergedAt : monitor.armedAt;
36221
+ const held = since !== null ? ` for ${humanDuration(Math.max(0, now - since))}` : "";
36222
+ if (monitor.latched) return monitor.verdict === "diverged" ? `CHANGED${held} — press Reset when you have dealt with it.` : `Back to normal, but still flagged as changed${held}. Press Reset to clear it.`;
36223
+ const paused = suspensionLine(monitor);
36224
+ if (paused !== null) return paused;
36225
+ return monitor.verdict === "diverged" ? `CHANGED${held}` : `Normal${held}`;
34272
36226
  }
34273
- /**
34274
- * Return a NEW array of `rows` ordered by `sort`.
36227
+ /** The badge word. `latched` wins over `verdict`, because the latch is the
36228
+ * thing the operator asked to be told and it outlives the live state.
34275
36229
  *
34276
- * `valueOf` is the column's `sortValue` — it is called once per row per
34277
- * comparison, so it must be cheap (read a field, do not format a string).
34278
- * `sort === null` ⇒ a fresh shallow copy in the incoming order.
34279
- */
34280
- function sortRowsByColumn(rows, sort, valueOf) {
34281
- if (sort === null) return [...rows];
34282
- const factor = sort.dir === "asc" ? 1 : -1;
34283
- return rows.toSorted((a, b) => {
34284
- const av = valueOf(a);
34285
- const bv = valueOf(b);
34286
- if (av === null || bv === null) {
34287
- if (av === bv) return 0;
34288
- return av === null ? 1 : -1;
34289
- }
34290
- return factor * compareValues(av, bv);
34291
- });
36230
+ * A scene sitting out an uncovered light reads `Paused`, in the NEUTRAL tone —
36231
+ * never `alarm`, and never the `danger` variant the page maps that to. A
36232
+ * missing IR reference must not be able to paint a red badge on a scene that
36233
+ * is doing exactly what it was configured to do. */
36234
+ function badgeFor(monitor) {
36235
+ if (monitor.states.length === 0) return {
36236
+ text: "Not armed",
36237
+ tone: "unknown"
36238
+ };
36239
+ if (monitor.latched) return {
36240
+ text: "Changed",
36241
+ tone: "alarm"
36242
+ };
36243
+ if (monitor.suspendedCondition !== null) return {
36244
+ text: "Paused",
36245
+ tone: "unknown"
36246
+ };
36247
+ if (monitor.verdict === "unknown") return {
36248
+ text: "Can’t tell",
36249
+ tone: "unknown"
36250
+ };
36251
+ return monitor.verdict === "diverged" ? {
36252
+ text: "Changed",
36253
+ tone: "alarm"
36254
+ } : {
36255
+ text: "Normal",
36256
+ tone: "ok"
36257
+ };
34292
36258
  }
34293
- /** Derive the `<th>` `aria-sort` for `columnKey` from the table's sort state. */
34294
- function ariaSortForColumn(sort, columnKey) {
34295
- if (sort === null || sort.columnKey !== columnKey) return "none";
34296
- return sort.dir === "asc" ? "ascending" : "descending";
36259
+ /** A rect covering most of the frame is dominated by weather, sky and parked
36260
+ * cars: the bin is 2 % of the pixels and CLIP will not notice it leaving. The
36261
+ * failure is silent INSENSITIVITY, which is the worst kind — hence a warning
36262
+ * rather than a refusal. */
36263
+ var ROI_AREA_WARN_FRACTION = .4;
36264
+ function roiTooLarge(roi) {
36265
+ return roi.width * roi.height > ROI_AREA_WARN_FRACTION;
36266
+ }
36267
+ function humanDuration(ms) {
36268
+ const s = Math.floor(ms / 1e3);
36269
+ if (s < 60) return `${s}s`;
36270
+ const m = Math.floor(s / 60);
36271
+ if (m < 60) return `${m}m`;
36272
+ const h = Math.floor(m / 60);
36273
+ if (h < 48) return `${h}h`;
36274
+ return `${Math.floor(h / 24)}d`;
34297
36275
  }
34298
36276
  //#endregion
34299
- //#region src/composites/data-table.tsx
36277
+ //#region src/composites/code-block.tsx
36278
+ function CodeBlock({ children, maxHeight = 300, className }) {
36279
+ const [copied, setCopied] = useState(false);
36280
+ const handleCopy = useCallback(() => {
36281
+ navigator.clipboard.writeText(children).then(() => {
36282
+ setCopied(true);
36283
+ setTimeout(() => setCopied(false), 2e3);
36284
+ });
36285
+ }, [children]);
36286
+ return /* @__PURE__ */ jsxs("div", {
36287
+ className: cn("relative group", className),
36288
+ children: [/* @__PURE__ */ jsx(ScrollArea, {
36289
+ style: { maxHeight },
36290
+ children: /* @__PURE__ */ jsx("pre", {
36291
+ className: "font-mono text-xs bg-surface p-3 rounded-md border border-border-subtle",
36292
+ children: /* @__PURE__ */ jsx("code", { children })
36293
+ })
36294
+ }), /* @__PURE__ */ jsx("div", {
36295
+ className: "absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity",
36296
+ children: /* @__PURE__ */ jsx(IconButton, {
36297
+ icon: copied ? Check : Copy,
36298
+ "aria-label": "Copy code",
36299
+ variant: "ghost",
36300
+ size: "sm",
36301
+ onClick: handleCopy
36302
+ })
36303
+ })]
36304
+ });
36305
+ }
36306
+ //#endregion
36307
+ //#region src/composites/confirm-action-button.tsx
34300
36308
  /**
34301
- * DataTable — shared responsive table for admin pages.
34302
- *
34303
- * Recipes baked in:
34304
- * - `overflow-x-auto` wrapper so narrow viewports don't clip the
34305
- * right edge (the rounded border survives the scroll).
34306
- * - `min-w-[Npx]` on the inner table so columns don't squash below
34307
- * readable width before the scroll kicks in.
34308
- * - `whitespace-nowrap` on headers — column titles never wrap to
34309
- * two lines (`AUTO-KILL` was the regression that triggered this).
34310
- * - Themed `bg-surface` + `border-border` so contrast works in both
34311
- * light and dark themes.
34312
- *
34313
- * Column model: caller supplies `columns: DataColumn<T>[]` and
34314
- * `rows: T[]`. Each column declares `header`, optional `align`,
34315
- * optional `nowrap`, and a `render(row) => ReactNode` that produces
34316
- * the cell content.
34317
- *
34318
- * <DataTable
34319
- * columns={[
34320
- * { key: 'username', header: 'Username', render: (u) => u.username },
34321
- * { key: 'role', header: 'Role', render: (u) => <RoleBadge .../> },
34322
- * { key: 'actions', header: '', align: 'right', render: (u) => <Actions .../> },
34323
- * ]}
34324
- * rows={users}
34325
- * minWidthPx={560}
34326
- * />
34327
- *
34328
- * If finer header markup is needed, pass `headerRender` instead of
34329
- * `header` (e.g. for a sortable click affordance).
34330
- *
34331
- * ── Narrow viewports ──────────────────────────────────────────────────────
34332
- * The table stays a table. The wrapper is `overflow-x-auto`, so a phone
34333
- * scrolls the columns sideways INSIDE the table — the page body never moves
34334
- * sideways, and the cells stay one line (`whitespace-nowrap`). That is the
34335
- * default (`mobileMode="scroll"`). Cards are opt-in via `mobileMode="cards"`;
34336
- * `auto` still applies the old column-count rule for a surface that wants it.
34337
- * See `resolveTableLayout`.
34338
- *
34339
- * ── Scrolling the BODY, not the page ──────────────────────────────────────
34340
- * `maxBodyHeight` caps the table and scrolls its rows INSIDE the container
34341
- * with the column header pinned. It is ONE prop, not two, because "this list
34342
- * scrolls internally" and "its header stays visible" are the same decision: a
34343
- * capped table whose header scrolls away is a list of unlabelled cells.
36309
+ * ConfirmActionButton — destructive-action button with a modal confirm.
34344
36310
  *
34345
- * Use it wherever the row count is open-ended (rules, targets, delivery
34346
- * history) so the page keeps its own header, filters and actions in view while
34347
- * the operator moves through the rows.
34348
- */
34349
- /**
34350
- * The default cap for an internally-scrolling table.
36311
+ * Two-step UX for any operation operators shouldn't trigger by accident:
36312
+ * reboot device, restart addon, regenerate token, etc. The trigger is a
36313
+ * normal Button that opens a Dialog; confirming runs the async action,
36314
+ * the trigger spinner-disables itself for the duration, and the dialog
36315
+ * closes on success. Errors surface inline at the dialog's bottom.
34351
36316
  *
34352
- * `60vh` leaves the page header, its filters and its actions on screen at
34353
- * every viewport while still showing ~10 rows — and it is a fraction, so it
34354
- * does not need a breakpoint of its own.
36317
+ * Generic on `TResult` so callers don't have to discard the return
36318
+ * value. Pass an `icon` to render it inside the trigger (e.g. RotateCw
36319
+ * for reboot, RefreshCw for restart).
34355
36320
  */
34356
- var TABLE_SCROLL_MAX_H = "max-h-[60vh]";
34357
- var ALIGN_CLASS = {
34358
- left: "text-left",
34359
- right: "text-right",
34360
- center: "text-center"
34361
- };
34362
- function DataTable({ columns, rows, rowKey, onRowClick, minWidthPx = 480, emptyMessage, className, bordered = true, rowClassName, mobileMode = "scroll", maxBodyHeight, defaultSort }) {
34363
- const isNarrow = useIsMobile();
34364
- const [sort, setSort] = useState(defaultSort ?? null);
34365
- const layout = resolveTableLayout({
34366
- mode: mobileMode,
34367
- columnCount: columns.length,
34368
- isNarrow
34369
- });
34370
- const scrolls = maxBodyHeight !== void 0;
34371
- const sortedColumn = useMemo(() => sort === null ? void 0 : columns.find((col) => col.key === sort.columnKey && col.sortValue !== void 0), [columns, sort]);
34372
- const sortValue = sortedColumn?.sortValue;
34373
- const displayRows = useMemo(() => sortValue === void 0 ? rows : sortRowsByColumn(rows, sort, sortValue), [
34374
- rows,
34375
- sort,
34376
- sortValue
34377
- ]);
34378
- const activeSort = sortedColumn === void 0 ? null : sort;
34379
- if (rows.length === 0 && emptyMessage) return /* @__PURE__ */ jsx("div", {
34380
- className: `rounded-lg ${bordered ? "border border-border" : ""} bg-surface px-3 py-4 text-xs text-foreground-subtle text-center ${className ?? ""}`,
34381
- children: emptyMessage
34382
- });
34383
- if (rows.length === 0) return null;
34384
- if (layout === "cards") return /* @__PURE__ */ jsx("div", {
34385
- className: `space-y-2 ${scrolls ? `${maxBodyHeight} overflow-y-auto` : ""} ${className ?? ""}`,
34386
- children: displayRows.map((row, rowIndex) => /* @__PURE__ */ jsx(DataTableCard, {
34387
- row,
34388
- rowIndex,
34389
- columns,
34390
- bordered,
34391
- onRowClick,
34392
- rowClassName
34393
- }, rowKey ? rowKey(row, rowIndex) : rowIndex))
34394
- });
34395
- return /* @__PURE__ */ jsx("div", {
34396
- className: [
34397
- "rounded-lg",
34398
- bordered ? "border border-border" : "",
34399
- "bg-surface overflow-x-auto",
34400
- scrolls ? `${maxBodyHeight} overflow-y-auto` : "",
34401
- className ?? ""
34402
- ].filter(Boolean).join(" "),
34403
- children: /* @__PURE__ */ jsxs("table", {
34404
- className: `w-full text-xs ${scrolls ? "border-separate border-spacing-0" : ""}`,
34405
- style: { minWidth: `${minWidthPx}px` },
34406
- children: [/* @__PURE__ */ jsx("thead", {
34407
- className: "bg-surface",
34408
- children: /* @__PURE__ */ jsx("tr", { children: columns.map((col) => {
34409
- const align = col.align ?? "left";
34410
- const nowrap = col.nowrap ?? true;
34411
- const sortable = col.sortValue !== void 0;
34412
- const isSorted = activeSort !== null && activeSort.columnKey === col.key;
34413
- return /* @__PURE__ */ jsx("th", {
34414
- "aria-sort": sortable ? ariaSortForColumn(activeSort, col.key) : void 0,
34415
- title: sortable ? void 0 : col.notSortableReason,
34416
- className: [
34417
- "px-3 py-2 text-foreground-subtle font-medium bg-surface border-b border-border",
34418
- scrolls ? "sticky top-0 z-10" : "",
34419
- ALIGN_CLASS[align],
34420
- nowrap ? "whitespace-nowrap" : "",
34421
- col.width ?? ""
34422
- ].filter(Boolean).join(" "),
34423
- children: sortable ? /* @__PURE__ */ jsx(SortHeaderButton, {
34424
- label: col.headerRender ? col.headerRender() : col.header,
34425
- active: isSorted,
34426
- dir: isSorted && activeSort !== null ? activeSort.dir : "asc",
34427
- onSort: () => setSort((prev) => nextTableSort(prev, col.key))
34428
- }) : col.headerRender ? col.headerRender() : col.header
34429
- }, col.key);
34430
- }) })
34431
- }), /* @__PURE__ */ jsx("tbody", { children: displayRows.map((row, rowIndex) => {
34432
- const key = rowKey ? rowKey(row, rowIndex) : rowIndex;
34433
- const extra = rowClassName?.(row, rowIndex) ?? "";
34434
- const interactive = onRowClick !== void 0;
34435
- return /* @__PURE__ */ jsx("tr", {
34436
- onClick: interactive ? () => onRowClick(row, rowIndex) : void 0,
34437
- className: [interactive ? "cursor-pointer hover:bg-primary/5" : "hover:bg-primary/5", extra].filter(Boolean).join(" "),
34438
- children: columns.map((col) => {
34439
- const align = col.align ?? "left";
34440
- const nowrap = col.nowrap ?? true;
34441
- return /* @__PURE__ */ jsx("td", {
34442
- className: [
34443
- "px-3 py-2 text-foreground border-b border-border",
34444
- ALIGN_CLASS[align],
34445
- nowrap ? "whitespace-nowrap" : ""
34446
- ].filter(Boolean).join(" "),
34447
- children: col.render(row, rowIndex)
34448
- }, col.key);
34449
- })
34450
- }, key);
34451
- }) })]
34452
- })
34453
- });
36321
+ function ConfirmActionButton({ label, icon: Icon, title = "Confirm action", description, confirmLabel, triggerVariant = "outline", confirmVariant = "danger", size = "sm", disabled, className, action, onSuccess }) {
36322
+ const [open, setOpen] = useState(false);
36323
+ const [running, setRunning] = useState(false);
36324
+ const [error, setError] = useState(null);
36325
+ const handleConfirm = async () => {
36326
+ setError(null);
36327
+ setRunning(true);
36328
+ try {
36329
+ const result = await action();
36330
+ onSuccess?.(result);
36331
+ setOpen(false);
36332
+ } catch (err) {
36333
+ setError(err instanceof Error ? err.message : String(err));
36334
+ } finally {
36335
+ setRunning(false);
36336
+ }
36337
+ };
36338
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Button, {
36339
+ type: "button",
36340
+ variant: triggerVariant,
36341
+ size,
36342
+ disabled,
36343
+ onClick: () => {
36344
+ setError(null);
36345
+ setOpen(true);
36346
+ },
36347
+ className,
36348
+ children: [Icon && /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" }), label]
36349
+ }), /* @__PURE__ */ jsx(Dialog, {
36350
+ open,
36351
+ onOpenChange: (next) => !running && setOpen(next),
36352
+ children: /* @__PURE__ */ jsxs(DialogContent, { children: [
36353
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsxs(DialogTitle, {
36354
+ className: "flex items-center gap-2",
36355
+ children: [/* @__PURE__ */ jsx(TriangleAlert, { className: "h-4 w-4 text-amber-400" }), title]
36356
+ }), typeof description === "string" ? /* @__PURE__ */ jsx(DialogDescription, { children: description }) : description] }),
36357
+ error && /* @__PURE__ */ jsx("div", {
36358
+ className: cn("mt-2 px-3 py-2 rounded border border-danger/40 bg-danger/10", "text-xs text-danger"),
36359
+ children: error
36360
+ }),
36361
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button, {
36362
+ type: "button",
36363
+ variant: "ghost",
36364
+ size,
36365
+ disabled: running,
36366
+ onClick: () => setOpen(false),
36367
+ children: "Cancel"
36368
+ }), /* @__PURE__ */ jsxs(Button, {
36369
+ type: "button",
36370
+ variant: confirmVariant,
36371
+ size,
36372
+ disabled: running,
36373
+ onClick: () => {
36374
+ handleConfirm();
36375
+ },
36376
+ children: [running && /* @__PURE__ */ jsx(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }), confirmLabel ?? label]
36377
+ })] })
36378
+ ] })
36379
+ })] });
34454
36380
  }
36381
+ //#endregion
36382
+ //#region src/composites/copy-button.tsx
34455
36383
  /**
34456
- * One row as a card. Columns that carry a plain-text `header` become labelled
34457
- * `SettingRow`s — the label is the column title and the value gets the full
34458
- * card width. Columns with no text header (an actions column, a
34459
- * `headerRender`-only column) have no label to show, so they render as a
34460
- * full-width strip at the foot of the card.
36384
+ * `CopyButton` — a compact button that copies a string `value` to the
36385
+ * clipboard via `navigator.clipboard.writeText`, showing a brief
36386
+ * "copied" check state. Addon-agnostic; used wherever a secret / URL
36387
+ * needs a one-click copy affordance (export setup panels, etc.).
34461
36388
  */
34462
- function DataTableCard({ row, rowIndex, columns, bordered, onRowClick, rowClassName }) {
34463
- const labelled = columns.filter((col) => col.header !== void 0 && col.header !== "");
34464
- const unlabelled = columns.filter((col) => col.header === void 0 || col.header === "");
34465
- const interactive = onRowClick !== void 0;
34466
- const extra = rowClassName?.(row, rowIndex) ?? "";
34467
- return /* @__PURE__ */ jsxs("div", {
34468
- "data-data-table-card": "",
34469
- onClick: interactive ? () => onRowClick(row, rowIndex) : void 0,
34470
- className: [
34471
- "rounded-lg bg-surface px-3 py-1.5 divide-y divide-border-subtle",
34472
- bordered ? "border border-border" : "",
34473
- interactive ? "cursor-pointer hover:bg-primary/5" : "",
34474
- extra
34475
- ].filter(Boolean).join(" "),
34476
- children: [labelled.map((col) => /* @__PURE__ */ jsx(SettingRow, {
34477
- label: col.header,
34478
- children: col.render(row, rowIndex)
34479
- }, col.key)), unlabelled.length > 0 && /* @__PURE__ */ jsx("div", {
34480
- className: "flex flex-wrap items-center justify-end gap-2 py-1.5",
34481
- children: unlabelled.map((col) => /* @__PURE__ */ jsx("div", { children: col.render(row, rowIndex) }, col.key))
34482
- })]
36389
+ var COPIED_RESET_MS = 2e3;
36390
+ function CopyButton({ value, label, srLabel, className, disabled }) {
36391
+ const [copied, setCopied] = useState(false);
36392
+ const handleCopy = useCallback(() => {
36393
+ if (!value) return;
36394
+ navigator.clipboard.writeText(value).then(() => {
36395
+ setCopied(true);
36396
+ setTimeout(() => setCopied(false), COPIED_RESET_MS);
36397
+ });
36398
+ }, [value]);
36399
+ return /* @__PURE__ */ jsxs(Button, {
36400
+ size: "sm",
36401
+ variant: "ghost",
36402
+ type: "button",
36403
+ disabled: disabled || value.length === 0,
36404
+ onClick: handleCopy,
36405
+ className: cn(className),
36406
+ "aria-label": copied ? "Copied" : `Copy ${srLabel ?? label ?? "value"}`,
36407
+ children: [copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 text-success" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" }), label ? /* @__PURE__ */ jsx("span", {
36408
+ className: "ml-1",
36409
+ children: copied ? "Copied" : label
36410
+ }) : null]
34483
36411
  });
34484
36412
  }
34485
36413
  //#endregion
@@ -54146,30 +56074,6 @@ function DoorbellRecentPanel({ history, limit = 5, latestIsFresh, className }) {
54146
56074
  });
54147
56075
  }
54148
56076
  //#endregion
54149
- //#region src/composites/section-card.tsx
54150
- function SectionCard({ headingId, title, icon: Icon, actions, testId, children }) {
54151
- return /* @__PURE__ */ jsxs("section", {
54152
- "aria-labelledby": headingId,
54153
- className: SECTION_CARD,
54154
- "data-testid": testId,
54155
- children: [/* @__PURE__ */ jsxs("header", {
54156
- className: `${SECTION_HEADER} flex min-h-9 flex-wrap items-center gap-2`,
54157
- children: [
54158
- Icon !== void 0 && /* @__PURE__ */ jsx(Icon, {
54159
- className: "h-3.5 w-3.5 shrink-0 text-foreground-subtle",
54160
- "aria-hidden": true
54161
- }),
54162
- /* @__PURE__ */ jsx("h2", {
54163
- id: headingId,
54164
- className: `${TEXT_SECTION_LABEL} min-w-0 flex-1`,
54165
- children: title
54166
- }),
54167
- actions
54168
- ]
54169
- }), children]
54170
- });
54171
- }
54172
- //#endregion
54173
56077
  //#region src/composites/filter-bar.tsx
54174
56078
  function FilterBar({ filters, values, onChange, className }) {
54175
56079
  const isMobile = useIsMobile();
@@ -54682,26 +56586,6 @@ function KebabMenu({ items, header, triggerClassName, title = "More actions" })
54682
56586
  });
54683
56587
  }
54684
56588
  //#endregion
54685
- //#region src/composites/key-value-list.tsx
54686
- /**
54687
- * A `<dl>` of label/value rows. Layout (including the L1 stacking) is owned by
54688
- * `SettingRow`; this composite only supplies the description-list semantics.
54689
- *
54690
- * The rows used to be `flex items-center h-7` with a `w-1/3` term, so a label
54691
- * could neither wrap nor stack: on a phone it took a third of the width and
54692
- * the value took whatever was left.
54693
- */
54694
- function KeyValueList({ items, className }) {
54695
- return /* @__PURE__ */ jsx("dl", {
54696
- className: cn("flex flex-col", className),
54697
- children: items.map((item) => /* @__PURE__ */ jsx(SettingRow, {
54698
- elements: "description",
54699
- label: item.key,
54700
- children: item.value
54701
- }, item.key))
54702
- });
54703
- }
54704
- //#endregion
54705
56589
  //#region src/composites/mount-addon-page.tsx
54706
56590
  /**
54707
56591
  * mountAddonPage — one-liner to mount an addon page in dev mode.
@@ -58851,323 +60735,6 @@ function useStationaryOverlay(trpc, deviceId) {
58851
60735
  return query.isError ? void 0 : query.data;
58852
60736
  }
58853
60737
  //#endregion
58854
- //#region src/hooks/turn-server-cache.ts
58855
- /** Short enough that rotated credentials are picked up promptly. */
58856
- var DEFAULT_FRESH_TTL_MS = 5 * 6e4;
58857
- /** Half of the shortest provider credential lifetime in the fleet (24 h). */
58858
- var DEFAULT_STALE_CAP_MS = 720 * 6e4;
58859
- var DEFAULT_STORAGE_KEY = "camstack:turn-servers:v1";
58860
- function defaultStorage() {
58861
- const g = globalThis;
58862
- try {
58863
- return g.localStorage ?? null;
58864
- } catch {
58865
- return null;
58866
- }
58867
- }
58868
- /** Type guard for a persisted entry — storage content is external data. */
58869
- function isCacheEntry(value) {
58870
- if (typeof value !== "object" || value === null) return false;
58871
- const v = value;
58872
- if (typeof v.fetchedAt !== "number" || !Array.isArray(v.servers)) return false;
58873
- return v.servers.every((s) => {
58874
- if (typeof s !== "object" || s === null) return false;
58875
- const srv = s;
58876
- if (!(typeof srv.urls === "string" || Array.isArray(srv.urls) && srv.urls.every((u) => typeof u === "string"))) return false;
58877
- if (srv.username !== void 0 && typeof srv.username !== "string") return false;
58878
- if (srv.credential !== void 0 && typeof srv.credential !== "string") return false;
58879
- return true;
58880
- });
58881
- }
58882
- var TurnServerCache = class {
58883
- freshTtlMs;
58884
- staleCapMs;
58885
- storage;
58886
- now;
58887
- storageKey;
58888
- /** Keyed on the caller's tRPC client object (stable per connected system). */
58889
- memory = /* @__PURE__ */ new WeakMap();
58890
- /** In-flight fetch per key — concurrent callers share one round trip. */
58891
- inflight = /* @__PURE__ */ new WeakMap();
58892
- constructor(options = {}) {
58893
- this.freshTtlMs = options.freshTtlMs ?? DEFAULT_FRESH_TTL_MS;
58894
- this.staleCapMs = options.staleCapMs ?? DEFAULT_STALE_CAP_MS;
58895
- this.storage = "storage" in options ? options.storage ?? null : defaultStorage();
58896
- this.now = options.now ?? (() => Date.now());
58897
- this.storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;
58898
- }
58899
- /**
58900
- * Resolve the ICE servers for `key`, fetching via `fetch` only when no
58901
- * fresh-enough entry exists. Never rejects: a failed fetch resolves to the
58902
- * stale entry when one exists, else `undefined`.
58903
- */
58904
- async getOrFetch(key, fetch) {
58905
- const at = this.now();
58906
- const entry = this.memory.get(key) ?? this.readStorage();
58907
- if (entry) {
58908
- const age = at - entry.fetchedAt;
58909
- if (age < this.freshTtlMs) return entry.servers;
58910
- if (age < this.staleCapMs) {
58911
- this.startFetch(key, fetch, entry);
58912
- return entry.servers;
58913
- }
58914
- }
58915
- return this.startFetch(key, fetch, entry);
58916
- }
58917
- startFetch(key, fetch, stale) {
58918
- const pending = this.inflight.get(key);
58919
- if (pending) return pending;
58920
- const run = (async () => {
58921
- try {
58922
- const servers = await fetch();
58923
- if (servers.length > 0) {
58924
- const entry = {
58925
- servers,
58926
- fetchedAt: this.now()
58927
- };
58928
- this.memory.set(key, entry);
58929
- this.writeStorage(entry);
58930
- }
58931
- return servers;
58932
- } catch {
58933
- return stale?.servers;
58934
- }
58935
- })().finally(() => {
58936
- this.inflight.delete(key);
58937
- });
58938
- this.inflight.set(key, run);
58939
- return run;
58940
- }
58941
- readStorage() {
58942
- if (!this.storage) return void 0;
58943
- try {
58944
- const raw = this.storage.getItem(this.storageKey);
58945
- if (raw === null) return void 0;
58946
- const parsed = JSON.parse(raw);
58947
- return isCacheEntry(parsed) ? parsed : void 0;
58948
- } catch {
58949
- return;
58950
- }
58951
- }
58952
- writeStorage(entry) {
58953
- if (!this.storage) return;
58954
- try {
58955
- this.storage.setItem(this.storageKey, JSON.stringify(entry));
58956
- } catch {}
58957
- }
58958
- };
58959
- //#endregion
58960
- //#region src/hooks/use-device-webrtc.ts
58961
- /**
58962
- * useDeviceWebrtc — WebRTC signaling hook for device-scoped streaming.
58963
- *
58964
- * Provides `createSession`, `sendAnswer`, `closeSession` callbacks and
58965
- * a `streams` list (Adaptive + High/Mid/Low) for a given device.
58966
- *
58967
- * Works in both admin-ui (BackendClient.trpc) and addon pages
58968
- * (AddonPageProps.trpc) — parametrized by a minimal tRPC proxy shape.
58969
- */
58970
- /**
58971
- * Hook that provides WebRTC signaling callbacks and stream choices for a device.
58972
- *
58973
- * @param trpc - tRPC proxy (admin-ui BackendClient.trpc or addon page trpc)
58974
- * @param deviceId - numeric device ID (null = disabled)
58975
- * @param pollIntervalMs - how often to refresh profile slots (default: 5000)
58976
- */
58977
- /** Module-level TURN/STUN credential cache. Keyed on the caller's trpc client
58978
- * object (stable per connected system — NOT on `trpc.turnProvider`, which is
58979
- * a proxy minted fresh on every property access), so two admin tabs pointed
58980
- * at different hubs never cross-serve credentials from the memory layer; the
58981
- * storage layer is per-origin, and the page's origin IS the hub. Coalesces
58982
- * concurrent fetches (the mount prefetch and the connect's own call used to
58983
- * race into TWO round trips) and persists across page loads so a fresh embed
58984
- * page — one per camera open on native — finds warm credentials instead of
58985
- * re-paying the measured 0.2–3.6 s serialized fetch. See turn-server-cache.ts. */
58986
- var turnServersCache = new TurnServerCache();
58987
- function useDeviceWebrtc(trpc, deviceId, pollIntervalMs = 5e3) {
58988
- const [remoteStreams, setRemoteStreams] = useState([]);
58989
- useEffect(() => {
58990
- if (deviceId === null) return;
58991
- let cancelled = false;
58992
- const fetch = () => {
58993
- trpc.webrtcSession.listStreams.query({ deviceId }).then((rows) => {
58994
- if (!cancelled) setRemoteStreams(rows);
58995
- }).catch(() => {});
58996
- };
58997
- fetch();
58998
- const iv = setInterval(fetch, pollIntervalMs);
58999
- return () => {
59000
- cancelled = true;
59001
- clearInterval(iv);
59002
- };
59003
- }, [
59004
- trpc,
59005
- deviceId,
59006
- pollIntervalMs
59007
- ]);
59008
- const [pipelineMetrics, setPipelineMetrics] = useState(null);
59009
- useEffect(() => {
59010
- if (deviceId === null) return;
59011
- let cancelled = false;
59012
- if (trpc.pipelineOrchestrator) trpc.pipelineOrchestrator.getCameraMetrics.query({ deviceId }).then((m) => {
59013
- if (cancelled || !m) return;
59014
- setPipelineMetrics({
59015
- phase: m.phase,
59016
- detectionFps: m.actualFps,
59017
- avgInferenceMs: m.avgInferenceTimeMs,
59018
- droppedFrames: m.droppedFrames
59019
- });
59020
- }).catch(() => {});
59021
- return () => {
59022
- cancelled = true;
59023
- };
59024
- }, [trpc, deviceId]);
59025
- useLiveEvent("pipeline.camera-metrics-snapshot", (evt) => {
59026
- if (deviceId === null) return;
59027
- const payload = evt.data;
59028
- if (payload.deviceId !== deviceId || !payload.metrics) return;
59029
- setPipelineMetrics({
59030
- phase: payload.metrics.phase,
59031
- detectionFps: payload.metrics.actualFps,
59032
- avgInferenceMs: payload.metrics.avgInferenceTimeMs,
59033
- droppedFrames: payload.metrics.droppedFrames
59034
- });
59035
- });
59036
- const { streams, hasWebrtc } = useMemo(() => {
59037
- if (deviceId === null || remoteStreams.length === 0) return {
59038
- streams: [],
59039
- hasWebrtc: false
59040
- };
59041
- return {
59042
- streams: remoteStreams.map((r) => ({
59043
- id: r.id,
59044
- label: r.label,
59045
- target: r.target,
59046
- status: r.status,
59047
- inputFps: r.inputFps,
59048
- decodeFps: r.decodeFps,
59049
- bitrateKbps: r.bitrateKbps
59050
- })),
59051
- hasWebrtc: true
59052
- };
59053
- }, [deviceId, remoteStreams]);
59054
- const getIceServers = useCallback(async () => {
59055
- const provider = trpc.turnProvider;
59056
- if (!provider) return void 0;
59057
- return turnServersCache.getOrFetch(trpc, async () => {
59058
- return (await provider.getTurnServers.query()).map((s) => {
59059
- return {
59060
- urls: typeof s.urls === "string" ? s.urls : [...s.urls],
59061
- ...s.username !== void 0 ? { username: s.username } : {},
59062
- ...s.credential !== void 0 ? { credential: s.credential } : {}
59063
- };
59064
- });
59065
- });
59066
- }, [trpc]);
59067
- useEffect(() => {
59068
- getIceServers();
59069
- }, [getIceServers]);
59070
- const createSession = useCallback(async (target, hints) => {
59071
- if (deviceId === null) throw new Error("No device selected");
59072
- const res = await trpc.webrtcSession.createSession.mutate({
59073
- deviceId,
59074
- target,
59075
- hints
59076
- });
59077
- const iceServers = await getIceServers();
59078
- return {
59079
- sessionId: res.sessionId,
59080
- sdpOffer: res.sdpOffer,
59081
- ...iceServers ? { iceServers } : {}
59082
- };
59083
- }, [
59084
- trpc,
59085
- deviceId,
59086
- getIceServers
59087
- ]);
59088
- const handleOffer = useCallback(async (target, sdpOffer, sessionId, hints) => {
59089
- if (deviceId === null) throw new Error("No device selected");
59090
- return trpc.webrtcSession.handleOffer.mutate({
59091
- deviceId,
59092
- target,
59093
- sdpOffer,
59094
- ...sessionId !== void 0 ? { sessionId } : {},
59095
- ...hints !== void 0 ? { hints } : {}
59096
- });
59097
- }, [trpc, deviceId]);
59098
- const sendAnswer = useCallback(async (sessionId, sdpAnswer) => {
59099
- if (deviceId === null) throw new Error("No device selected");
59100
- await trpc.webrtcSession.handleAnswer.mutate({
59101
- deviceId,
59102
- sessionId,
59103
- sdpAnswer
59104
- });
59105
- }, [trpc, deviceId]);
59106
- return {
59107
- streams,
59108
- hasWebrtc,
59109
- pipelineMetrics,
59110
- createSession,
59111
- handleOffer,
59112
- getIceServers,
59113
- addIceCandidate: useCallback(async (sessionId, candidate) => {
59114
- if (deviceId === null) return;
59115
- await trpc.webrtcSession.addIceCandidate.mutate({
59116
- deviceId,
59117
- sessionId,
59118
- candidate: candidate.candidate ?? "",
59119
- sdpMid: candidate.sdpMid ?? null,
59120
- sdpMLineIndex: candidate.sdpMLineIndex ?? null
59121
- }).catch(() => {});
59122
- }, [trpc, deviceId]),
59123
- getIceCandidates: useCallback(async (sessionId) => {
59124
- if (deviceId === null) return {
59125
- candidates: [],
59126
- done: true
59127
- };
59128
- return trpc.webrtcSession.getIceCandidates.query({
59129
- deviceId,
59130
- sessionId
59131
- });
59132
- }, [trpc, deviceId]),
59133
- sendAnswer,
59134
- closeSession: useCallback(async (sessionId) => {
59135
- if (deviceId === null) return;
59136
- await trpc.webrtcSession.closeSession.mutate({
59137
- deviceId,
59138
- sessionId
59139
- }).catch(() => {});
59140
- }, [trpc, deviceId]),
59141
- getSessionState: useCallback(async (sessionId) => {
59142
- if (deviceId === null) return { pendingRenegotiation: null };
59143
- try {
59144
- return await trpc.webrtcSession.getSessionState.query({
59145
- deviceId,
59146
- sessionId
59147
- });
59148
- } catch {
59149
- return { pendingRenegotiation: null };
59150
- }
59151
- }, [trpc, deviceId]),
59152
- getSessionDebug: useCallback(async (sessionId) => {
59153
- if (deviceId === null) throw new Error("getSessionDebug: no device bound to this player");
59154
- return trpc.webrtcSession.getSessionDebug.query({
59155
- deviceId,
59156
- sessionId
59157
- });
59158
- }, [trpc, deviceId]),
59159
- applyDownlinkProbe: useCallback(async (sessionId, downlinkMbps) => {
59160
- const mutate = trpc.webrtcSession.applyDownlinkProbe;
59161
- if (deviceId === null || mutate === void 0) throw new Error("applyDownlinkProbe: not available for this player");
59162
- return mutate.mutate({
59163
- deviceId,
59164
- sessionId,
59165
- downlinkMbps
59166
- });
59167
- }, [trpc, deviceId])
59168
- };
59169
- }
59170
- //#endregion
59171
60738
  //#region src/hooks/use-doorbell-events.ts
59172
60739
  /**
59173
60740
  * useDoorbellEvents — subscribe to doorbell.onPressed across the cluster.
@@ -59440,4 +61007,4 @@ var SceneMonitorEditor = lazy(() => import("./SceneMonitorEditor-vfWTm6qv.js").t
59440
61007
  /** Lazy-wrapped `PrivacyMaskSettings` — code-split off the main bundle. */
59441
61008
  var PrivacyMaskSettings = lazy(() => import("./PrivacyMaskSettings-DMkchqO1.js").then((m) => ({ default: m.PrivacyMaskSettings })));
59442
61009
  //#endregion
59443
- export { AddonGlobalSettingsForm, AdminLinkQuickAction, AgentStepEditor, AlarmHeroCard, AlarmInlineControl as AlarmPanelInlineControl, AppShell, ArcKnob, AudioClassificationList, AudioLevelWaveform, AudioWaveform, AutomationHeroCard, AutomationInlineControl, AutotrackSection, BTN_COMPACT, BTN_COMPACT_DANGER, BTN_COMPACT_PRIMARY, BTN_COMPACT_WARNING, Badge, BatteryBadge, BottomSheet, Breadcrumb, BrightnessPanel, Button, ButtonControl, ButtonHeroCard, CARD_MODE_MIN_COLUMNS, CENTER, CHIP_ACTIVE, CHIP_BASE, CHIP_INACTIVE, CLASS_COLORS, COLUMN_BREAKPOINT_CLASS, COLUMN_PRIORITY, COMMIT_DEDUPE_TOLERANCE_MS, COMMIT_DEDUPE_WINDOW_MS, CONTROL_CAP_NAMES, CONTROL_FILLS, CameraStreamPlayer, Card, Checkbox, ChildSectionAccordion, ClimatePanel, CodeBlock, CollapsibleCard, ConfigFormBuilder, FormField as ConfigFormField, ConfigNotificationTargetMultiSelectFieldView, ConfigNotificationTargetSelectFieldView, ConfigSchemaField, ConfirmActionButton, ConfirmDialogProvider, ConsumablesPanel, ContainerChildrenProvider, ContainerPrimaryHero, ControlColumn, ControlHeroCard, ControlInlineControl, ControlPanel, CopyButton, CoverHeroCard, CoverInlineControl, CoverPanel, CustomFieldRenderersProvider, DEFAULT_COLOR, DEVICE_COLUMNS, DEVICE_LIST_PAGE_SIZE_KEY, DEVICE_LIST_PAGE_SIZE_OPTIONS, DEVICE_PICKER_GROUP_STORAGE_KEY, DEVICE_ROLE_META, DEVICE_TYPE_CONTROL, DEVICE_TYPE_META, DISPLAY_ICON_REGISTRY, DataTable, DataTableFilterBar, DetectionCanvas, DetectionOverlay, DetectionResultTree, DevShell, DeviceActivityPanel, DeviceBatchToolbar, DeviceCard, DeviceContextProvider, DeviceExportPanel, DeviceFilterBar, DeviceGrid, DeviceItem, DeviceList, DeviceMultiSelectField, DevicePicker, DeviceQuickDetailProvider, DeviceSelectField, DeviceSelectorPicker, DeviceStepMatrix, DevicesFailedLine, DevicesLoadingLine, DevicesWidenWarning, Dialog, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, DiscoveryPanel, DoorbellRecentPanel, Dropdown, DropdownContent, DropdownItem, DropdownTrigger, DummyHeroCard, DummyInline, EMPTY_DATA_TABLE_FILTER, EVENT_KIND_ICONS, EmptyState, ErrorBox, EventKindGlyph, EventStream, ExtensionsPanel, FILL, FLEET_LIST_GC_TIME_MS, FLEET_LIST_QUERY_KEY, FLEET_LIST_STALE_TIME_MS, FanHeroCard, FanInlineControl, FanPanel, FilterBar, FloatingEventStream, FloatingLogStream, FloatingPanel, FormField$1 as FormField, GRID_GAP, GRID_PAIRED, GRID_QUICK_STATS, GripTrack, GroupedModelSelector, HOST_WIDGETS, HlsVideo, HoverZoomImage, HumidifierHeroCard, HumidifierInlineControl, INPUT_COMPACT, INPUT_SHELL, IconAction, IconButton, IconPicker, ImageHeroCard, ImageInlineControl, ImageSelector, InferenceConfigSelector, Input, KebabMenu, KeyValueList, LIST_ROW, Label, LawnMowerHeroCard, LawnMowerInlineControl, LightHeroCard, LightInlineControl, LockHeroCard, LockInlineControl, LockPanel, LogStream, LoginForm, MODE_COLOR, MaskShapeCanvas, MediaPlayerHeroCard, MediaPlayerInlineControl, MediaPlayerPanel, MobileDrawer, ModelPicker, MotionZonesSettings, NAVIGATION_DRIVE_INTERVAL_MS, NavigationPanel, NetworkLinkBadge, NodeMultiSelectField, NodePicker, NodeSelectField, NotificationTargetSelectField, NotificationTargetsField, OPTION_SELECT_SEARCH_MIN_OPTIONS, OfflineBadge, OptionSelect, PHASE_CONFIG, PICKER_CHIP_LIMIT, PICKER_DEFAULT_GROUP_BY, PICKER_GROUP_BY_MODES, PICKER_GROUP_COLLAPSE_AT, PICKER_NONE_KEY, PICKER_NOUN_PLURAL, PICKER_NO_FILTERS, PICKER_PAGE_SIZE, PICKER_SEARCH_DEBOUNCE_MS, PICKER_WINDOW, PRIORITY, PTZOverlay, PageHeader, PhaseIcon, PipelineBuilder, PipelineRuntimeSelector, PipelineStep, PipelineTreeMatrix, PlaceholderChipField, PlayerOverlaysProvider, Popover, PopoverContent, PopoverRowAction, PopoverTrigger, PrimaryChildPicker, PrivacyMaskSettings, ProviderBadge, PtzPanel, QrCode, RECONNECT_POLICY, RECORDED_PLAYBACK_MODES, RIGHT, ROI_AREA_WARN_FRACTION, ROLE_DESCRIPTOR, RadialGauge, RecordedPlaybackProvider, RecordingPanel, ResponseLog, RoleDisplayDefaultsFromServer, RoleDisplayDefaultsProvider, SCENE_CAPTURE_VARIANTS, SECTION_BODY, SECTION_CARD, SECTION_HEADER, SETTING_ROW, SETTING_ROW_LABEL, SETTING_ROW_STACK_BREAKPOINT, SETTING_ROW_VALUE, SETTING_ROW_VALUE_TEXT, SNAPSHOT_MEDIA_PATH, SPLIT_PANEL_OUTER, SPLIT_PANEL_SIDE, STACK_GAP, STATE_COLOR, SceneMonitorEditor, ScopePicker, ScriptHeroCard, ScriptInlineControl, ScrollArea, SectionCard, Select, SelectedChip, SemanticBadge, SensorHeroCard, SensorInlineControl, SensorValueAtom, Separator, SettingRow, Sidebar, SidebarItem, Skeleton, SlideOverPanel, SlideToggle, SnapshotButton, SortHeaderButton, StatCard, StateValuesStream, StatusBadge, StepTimings, StepTreeMaster, Stepper, StreamBrokerSelector, StreamDebugOverlay, StreamPanel, Switch, SwitchHeroCard, SwitchInlineControl, SwitchPanel, SystemProvider, TABLE_SCROLL_MAX_H, TEXT_FIELD_LABEL, TEXT_HINT, TEXT_METRIC, TEXT_SECTION_LABEL, TEXT_VALUE, TIMEZONES, Tabs, TabsContent, TabsList, TabsTrigger, TapToggle, ThemeProvider, ThermostatHeroCard, ThermostatInlineControl, TimezoneSelector, Tooltip, TooltipContent, TooltipTrigger, UnitPicker, VacuumHeroCard, VacuumInlineControl, ValueReadout, ValveHeroCard, ValveInlineControl, VersionBadge, VodPlaybackProvider, WaterHeaterHeroCard, WaterHeaterInlineControl, WeatherHeroCard, WeatherInlineControl, WidgetMetricCard, WidgetPanel, WidgetRegistryProvider, WidgetSlot, ZoneEditingProvider, MicOff as a, agentColumnKey, allDeviceTypeFilterOptions, applyFleetQueryDefaults, applyPickerFilters, ariaSortForColumn, badgeFor, buildStepTreeFromSchema, EyeOff as c, catalogHasDraft, childEntityId, childListName, chipOverflow, classifyCapability, clearPickerGroup, cn, columnsForContext, containerChildToRef, countableDevices, coverHighlight, coverageLine, createSharedContext, createTheme, cursorFractionFor, darkColors, dataTableFacets, defaultTheme, deriveDeviceKind, deriveWebrtcDebugStats, describeDeviceSelector, deviceIntegrationResolver, deviceListStateOf, deviceMatchesFilter, deviceOptionLabel, deviceRoleMeta, deviceRoleMetaOf, deviceTypeLabel, deviceTypeMeta, deviceTypeMetaOf, devicesToOptions, draftFromQuery, emptyDevicesWidens, ensureMfHostInit, eventKindLabel, filterDataTableRows, filterDeviceOptions, filterOptions as filterOptionSelectOptions, filterPickerDevices, findTimezone, flattenPickerGrouping, flattenPickerRows, fleetViewOf, formatControlDateTime, formatLastSeen, formatNumeric, freezeVerdict, fuzzyMatch, getClassColor, getPhaseVisual, groupAgentColumns, groupChildrenByLayout, groupPickerDevices, groupPickerDevicesBy, hardwareLabel, hubOrigin, hubPath, hubUrl, hubWsUrl, humanDuration, humidifierTint, Mic as i, initialScrubState, insertAtSelection, isAbsentProvider, isDataTableFilterActive, isFieldVisible, isGroupExpanded, Camera as l, lawnMowerActivityMeta, lightColors, loadRemoteBundle, makeScrubBridge, metadataEntries, metadataString, mirror, mountAddonPage, mountPath, Square as n, nextReconnectAction, nextSort, nextSortDirection, nextTableSort, normalizeForSearch, ImageOff as o, offListSelections, openTopLayer, optionSearchText as optionSelectSearchText, orphanDeviceLabel, overrideEntityIdFromLink, pageRows, parentDetailOf, parsePickerGroupBy, parseRecordedServerMessage, partitionSelected as partitionOptionSelectSelected, partitionSuggested, pickerFacetFilterShown, pickerFacetKey, pickerFacetOptions, pickerFiltersActive, pickerGroupKey, pickerHiddenSelectedCount, pickerLocationKey, pickerQueryTokens, pickerRowFacts, pickerSearchText, pickerZoneFilterSuspended, providerIcons, RotateCcw as r, readStoredGroupBy, resetHubMountForTests, resetPickerFilters, resolveContainerPrimary, resolveControlAlign, resolveDeviceControl, resolveDeviceIntegration, resolveDisplayIcon, resolveEffectiveDefaultModel, resolveEffectiveStepModel, resolveEventKindIcon, resolveSearchable as resolveOptionSelectSearchable, resolveParentNames, resolvePrimaryChild, resolveSelectedDevices, resolveSensorDisplay, resolveStepDefaultModel, resolveTableLayout, roiTooLarge, rootSourceState, routerBasename, Grid2x2 as s, sameLocationSuggestions, scrubReducer, selectPickerGroup, selectedDeviceOptions, serializeRecordedCommand, shouldCommit, shouldEmit, shouldShowAddNewFooter, shouldShowUseQueryRow, shouldUseSingleNode, showsNoCamerasHint, sortRows, sortRowsByColumn, statusIcons, statusLine, stepHasModelForFormat, stepModelOptions, stripParentNamePrefix, Trash2 as t, tabForSelector, tankAlert, themeToCss, toStationaryOverlayBox, toggleDataTableFacet, togglePickerDevice, togglePickerKey, trpc, typeLabel, createLucideIcon as u, useAccessoriesGetStatus, useAccessoriesSetChildHidden, useAddonPagesListPages, useAddonSettingsGetDeviceSettings, useAddonSettingsGetGlobalSettings, useAddonSettingsGetIntegrationSettings, useAddonSettingsUpdateDeviceSettings, useAddonSettingsUpdateGlobalSettings, useAddonWidgetsListWidgets, useAddonsApplyAutoUpdateToAll, useAddonsCancelJob, useAddonsCustom, useAddonsForceRefresh, useAddonsGetAddonAutoUpdate, useAddonsGetAutoUpdateSettings, useAddonsGetJob, useAddonsGetLastRestart, useAddonsGetLogs, useAddonsGetVersions, useAddonsInstallFromWorkspace, useAddonsInstallPackage, useAddonsIsWorkspaceAvailable, useAddonsList, useAddonsListCapabilityProviders, useAddonsListFrameworkPackages, useAddonsListJobs, useAddonsListPackages, useAddonsListUpdates, useAddonsListWorkspacePackages, useAddonsOnAddonLogs, useAddonsReloadPackages, useAddonsRestartAddon, useAddonsRestartServer, useAddonsRetryLoad, useAddonsRollbackPackage, useAddonsSearchAvailable, useAddonsSetAddonAutoUpdate, useAddonsSetAutoUpdateSettings, useAddonsSetCapabilityProviderEnabled, useAddonsStartJob, useAddonsUninstallPackage, useAddonsUpdatePackage, useAirQualitySensorGetStatus, useAlarmPanelArm, useAlarmPanelDisarm, useAlarmPanelGetStatus, useAlarmPanelTrigger, useAlert, useAlertsDismiss, useAlertsEmit, useAlertsGetUnreadCount, useAlertsList, useAlertsMarkAllRead, useAlertsMarkRead, useAlertsUpdate, useAllWidgets, useAmbientLightSensorGetStatus, useAudioAnalysisApplyDeviceSettingsPatch, useAudioAnalysisGetDeviceLiveContribution, useAudioAnalysisGetDeviceSettingsContribution, useAudioAnalysisResolveDeviceSettings, useAudioAnalyzerAnalyseChunk, useAudioAnalyzerAttachDevice, useAudioAnalyzerClassify, useAudioAnalyzerDetachDevice, useAudioAnalyzerDispose, useAudioAnalyzerIsReady, useAudioAnalyzerReprobeAudioEngine, useAudioCodecCanHandle, useAudioCodecCloseSession, useAudioCodecCreateDecodeSession, useAudioCodecCreateEncodeSession, useAudioCodecFlushEncode, useAudioCodecListActiveSessions, useAudioCodecListSupportedCodecs, useAudioCodecPullEncoded, useAudioCodecPullPcm, useAudioCodecPushEncodedFrame, useAudioCodecPushPcm, useAudioMetricsGetCurrentSnapshot, useAudioMetricsGetHistory, useAutomationControlDisable, useAutomationControlEnable, useAutomationControlGetStatus, useAutomationControlTrigger, useBackupCancel, useBackupDelete, useBackupDeleteSchedule, useBackupGetEntries, useBackupList, useBackupListArchives, useBackupListDestinations, useBackupListLocations, useBackupListRuns, useBackupListSchedules, useBackupPreviewSchedule, useBackupRestore, useBackupTrigger, useBackupUpsertDestinationPolicy, useBackupUpsertSchedule, useBatteryGetStatus, useBatteryWakeForStream, useBinaryGetStatus, useBrightnessGetStatus, useBrightnessSetBrightness, useBrokerAdd, useBrokerGet, useBrokerGetBrokerConfig, useBrokerGetSettings, useBrokerGetSettingsSchema, useBrokerGetState, useBrokerGetStatus, useBrokerList, useBrokerListProviders, useBrokerPublish, useBrokerRemove, useBrokerSetSettings, useBrokerSubscribe, useBrokerTestConnection, useBrokerTestSettings, useBrokerUnsubscribe, useButtonPress, useCalendarsGetPreferences, useCalendarsListCalendars, useCalendarsListEvents, useCalendarsSetCalendarPreference, useCameraCredentialsGetCredentials, useCameraCredentialsGetStatus, useCameraGridLayoutApplyDeviceSettingsPatch, useCameraGridLayoutGetDeviceLiveContribution, useCameraGridLayoutGetDeviceSettingsContribution, useCameraGridLayoutGetLayout, useCameraGridLayoutSaveLayout, useCameraPipelineConfigApplyDeviceSettingsPatch, useCameraPipelineConfigGetDeviceLiveContribution, useCameraPipelineConfigGetDeviceSettingsContribution, useCameraStreamsGetBrokerStreams, useCameraStreamsGetCameraStreams, useCameraStreamsGetProfileRtspEntries, useCameraStreamsGetRtspEntries, useCameraStreamsPickStream, useCapabilityPresence, useCarbonMonoxideGetStatus, useClimateControlGetStatus, useClimateControlSetFanMode, useClimateControlSetMode, useClimateControlSetPreset, useClimateControlSetSwingHorizontal, useClimateControlSetSwingVertical, useClimateControlSetTarget, useClimateControlSetTargetHumidity, useClimateControlSetTargetRange, useClusterNodes, useClusterTopology, useColorGetStatus, useColorSetColor, useConfirm, useConnectionTestDescribeTest, useConnectionTestTestSettings, useConnectivityGetStatus, useConsumablesGetStatus, useConsumablesReset, useContactGetStatus, useContainerChildren, useControlGetStatus, useControlSetValue, useCoreBlocksCompile, useCoreBlocksCreate, useCoreBlocksCreateComposition, useCoreBlocksDelete, useCoreBlocksGet, useCoreBlocksGetBlockSettings, useCoreBlocksGetComposition, useCoreBlocksGetCustomization, useCoreBlocksGetTypeDefs, useCoreBlocksList, useCoreBlocksListComposableCaps, useCoreBlocksListCompositions, useCoreBlocksRestart, useCoreBlocksSetEnabled, useCoreBlocksUpdate, useCoreBlocksUpdateBlockSettings, useCoreBlocksUpdateComposition, useCoreBlocksValidateComposition, useCoverClose, useCoverGetStatus, useCoverOpen, useCoverSetPosition, useCoverSetTiltPosition, useCoverStop, useCustomFieldRenderer, useDataTableFilter, useDayNightGetOptions, useDayNightGetStatus, useDayNightSetSettings, useDebouncedString, useDecoderCreateSession, useDecoderDestroySession, useDecoderGetFrame, useDecoderGetInfo, useDecoderGetShmStats, useDecoderGetStats, useDecoderListActiveSessions, useDecoderOpenStream, useDecoderPullFrames, useDecoderPullHandles, useDecoderPushPacket, useDecoderReprobeHwaccel, useDecoderSupportsCodec, useDecoderUpdateConfig, useDetectionPipelineApplyDeviceSettingsPatch, useDetectionPipelineGetDeviceLiveContribution, useDetectionPipelineGetDeviceSettingsContribution, useDevShell, useDevice, useDeviceAdminLink, useDeviceAdminLinkGetAdminLink, useDeviceAdoptionAdopt, useDeviceAdoptionGetCandidate, useDeviceAdoptionGetStatus, useDeviceAdoptionListCandidateFilters, useDeviceAdoptionListCandidates, useDeviceAdoptionRefresh, useDeviceAdoptionRelease, useDeviceAdoptionResync, useDeviceAutotrack, useDeviceBattery, useDeviceCapSlice, useDeviceCapability, useDeviceDetections, useDeviceDiscoveryAdoptDevice, useDeviceDiscoveryGetStatus, useDeviceDiscoveryListDiscovered, useDeviceDiscoveryRefreshDiscovery, useDeviceDiscoveryReleaseDevice, useDeviceExportApplyDeviceSettingsPatch, useDeviceExportExposeDevice, useDeviceExportGetDeviceLiveContribution, useDeviceExportGetDeviceSettingsContribution, useDeviceExportGetStatus, useDeviceExportListExposedDevices, useDeviceExportListSupportedDeviceKinds, useDeviceExportUnexposeDevice, useDeviceExtensionListCandidates, useDeviceExtensionListSlots, useDeviceExtensionListSources, useDeviceExtensionSetSlot, useDeviceId, useDeviceListPageSize, useDeviceManagerAddLocation, useDeviceManagerAdoptDevice, useDeviceManagerAdoptionAdopt, useDeviceManagerAdoptionCancelJob, useDeviceManagerAdoptionListCandidateFilters, useDeviceManagerAdoptionListCandidates, useDeviceManagerAdoptionListJobs, useDeviceManagerAdoptionRefresh, useDeviceManagerAdoptionRelease, useDeviceManagerAdoptionResync, useDeviceManagerAdoptionStartJob, useDeviceManagerAllocateDeviceId, useDeviceManagerApplyInitialMeta, useDeviceManagerCreateDevice, useDeviceManagerDisable, useDeviceManagerDiscoverAllProviders, useDeviceManagerDiscoverDevices, useDeviceManagerDiscoverProvider, useDeviceManagerDiscoveryProviders, useDeviceManagerEnable, useDeviceManagerGetAllBindings, useDeviceManagerGetBindings, useDeviceManagerGetBindingsBatch, useDeviceManagerGetChildren, useDeviceManagerGetChildrenBatch, useDeviceManagerGetConfigSchema, useDeviceManagerGetCreationSchema, useDeviceManagerGetDevice, useDeviceManagerGetDeviceAggregate, useDeviceManagerGetDeviceLiveInfoAggregate, useDeviceManagerGetDeviceSettingsAggregate, useDeviceManagerGetDeviceStatusAggregate, useDeviceManagerGetDeviceStatusAggregateBatch, useDeviceManagerGetLinkedDevices, useDeviceManagerGetLinkedDevicesBatch, useDeviceManagerGetRoleDisplayDefaults, useDeviceManagerGetSettingsSchema, useDeviceManagerGetStreamProfileMap, useDeviceManagerGetStreamSources, useDeviceManagerGetWireableFields, useDeviceManagerGetWrapperActivation, useDeviceManagerListAll, useDeviceManagerListBindableCapsForDeviceType, useDeviceManagerListDeviceIdsByCaps, useDeviceManagerListLocationDescriptions, useDeviceManagerListLocationIcons, useDeviceManagerListLocations, useDeviceManagerListPersistedByAddon, useDeviceManagerListSiteZones, useDeviceManagerListWrappersForCap, useDeviceManagerLoadConfig, useDeviceManagerLoadMeta, useDeviceManagerLoadRuntimeState, useDeviceManagerMigrateDevice, useDeviceManagerPersistConfig, useDeviceManagerProbeStreams, useDeviceManagerProviderCreationType, useDeviceManagerProviderDiscoveryParamsSchema, useDeviceManagerRegisterDevice, useDeviceManagerRemove, useDeviceManagerRemoveByIntegration, useDeviceManagerRemoveDevice, useDeviceManagerRemoveLocation, useDeviceManagerRemoveSiteZone, useDeviceManagerRenameLocation, useDeviceManagerRenameSiteZone, useDeviceManagerRunDeviceAction, useDeviceManagerSetChildLayout, useDeviceManagerSetDisabled, useDeviceManagerSetDisplay, useDeviceManagerSetIntegrationId, useDeviceManagerSetLinkDeviceId, useDeviceManagerSetLocation, useDeviceManagerSetLocationDescription, useDeviceManagerSetLocationIcon, useDeviceManagerSetLocationZone, useDeviceManagerSetMetadata, useDeviceManagerSetName, useDeviceManagerSetPrimaryChildEntityId, useDeviceManagerSetRole, useDeviceManagerSetRoleDisplayDefaults, useDeviceManagerSetSiteZone, useDeviceManagerSetStreamProfileMap, useDeviceManagerSetType, useDeviceManagerSetWrapperActive, useDeviceManagerTestCreationField, useDeviceManagerTestField, useDeviceManagerUpdateConfig, useDeviceManagerUpdateDeviceField, useDeviceManagerUpdateDeviceFieldsBatch, useDeviceNetworkLink, useDeviceOpsGetConfigEntries, useDeviceOpsGetRawState, useDeviceOpsGetSettingsSchema, useDeviceOpsGetStreamSources, useDeviceOpsRemoveDevice, useDeviceOpsRunAction, useDeviceOpsSetConfig, useDeviceProviderAdoptDiscoveredDevice, useDeviceProviderCreateDevice, useDeviceProviderDiscoverDevices, useDeviceProviderGetChildCreationSchema, useDeviceProviderGetDevices, useDeviceProviderGetDiscoveryParamsSchema, useDeviceProviderGetManualCreationType, useDeviceProviderGetStatus, useDeviceProviderReloadDevice, useDeviceProviderStart, useDeviceProviderStop, useDeviceProviderSupportsDiscovery, useDeviceProviderSupportsManualCreation, useDeviceProviderTestCreationField, useDeviceProxy, useDeviceQuickDetailOpen, useDeviceSnapshot, useDeviceSnapshotImage, useDeviceState, useDeviceStateClaimFields, useDeviceStateGetAllSnapshots, useDeviceStateGetCapSlice, useDeviceStateGetNativeCapSlice, useDeviceStateGetSnapshot, useDeviceStateListClaims, useDeviceStatePatchOwnedFields, useDeviceStateReleaseClaim, useDeviceStateSetCapSlice, useDeviceStateSlice, useDeviceStatusGetStatus, useDeviceWebrtc, useDevices, useDoorbellEvents, useDoorbellGetStatus, useEnumSensorGetStatus, useEventEmitterGetStatus, useEventInvalidation, useEventStreamLatest, useEventStreamMap, useFaceGalleryAssignFace, useFaceGalleryAssignFaces, useFaceGalleryAuditIdentitySamples, useFaceGalleryCreateIdentity, useFaceGalleryDeleteFace, useFaceGalleryDeleteIdentity, useFaceGalleryGetFaceByTrack, useFaceGalleryGetFaceMedia, useFaceGalleryListIdentities, useFaceGalleryListIdentitySamples, useFaceGalleryListRecentFaces, useFaceGalleryRemoveSample, useFaceGalleryRenameIdentity, useFaceGallerySuggestFaceClusters, useFaceGalleryUnassignFace, useFaceGalleryUnassignFaces, useFanControlGetStatus, useFanControlSetDirection, useFanControlSetOscillating, useFanControlSetPercentage, useFanControlSetPreset, useFeatureProbeGetStatus, useFloodGetStatus, useGasGetStatus, useHumidifierGetStatus, useHumidifierSetMode, useHumidifierSetOn, useHumidifierSetTargetHumidity, useHumiditySensorGetStatus, useImageGetStatus, useImageSettingsGetOptions, useImageSettingsGetStatus, useImageSettingsSetSettings, useIntegrationsCreate, useIntegrationsDelete, useIntegrationsGet, useIntegrationsGetAvailableTypes, useIntegrationsGetByAddonId, useIntegrationsGetSettings, useIntegrationsList, useIntegrationsSetSettings, useIntegrationsTestConnection, useIntegrationsUpdate, useIntercomEndTalkSession, useIntercomGetStatus, useIntercomHandleAnswer, useIntercomPushTalkAudio, useIntercomStartSession, useIntercomStartTalkSession, useIntercomStopSession, useIsMidWidth, useIsMobile, useLawnMowerControlDock, useLawnMowerControlGetStatus, useLawnMowerControlPause, useLawnMowerControlStartMowing, useLiveBuffer, useLiveEvent, useLlmCancel, useLlmDeleteModel, useLlmDeleteProfile, useLlmGenerate, useLlmGenerateVision, useLlmGetDefaults, useLlmGetRuntimeStatus, useLlmGetUsage, useLlmInstallModel, useLlmListModelCatalog, useLlmListModels, useLlmListNodeModels, useLlmListProfileKinds, useLlmListProfiles, useLlmListRuntimeNodes, useLlmResolveModelRef, useLlmSetDefault, useLlmStartRuntime, useLlmStopRuntime, useLlmTestProfile, useLlmUpsertProfile, useLocalNetworkAddCustomAddress, useLocalNetworkDownloadCa, useLocalNetworkGetAddresses, useLocalNetworkGetAllowedAddresses, useLocalNetworkGetConnectionEndpoints, useLocalNetworkGetHubAddresses, useLocalNetworkGetNotificationEndpoint, useLocalNetworkGetPreferred, useLocalNetworkGetTlsStatus, useLocalNetworkGetViewerEndpoints, useLocalNetworkList, useLocalNetworkRegenerateCertificate, useLocalNetworkRemoveCustomAddress, useLocalNetworkResetAllowlistToBestMatch, useLocalNetworkRevertToGeneratedCertificate, useLocalNetworkSetAddressRole, useLocalNetworkSetAllowedAddresses, useLocalNetworkSetNotificationEndpoint, useLocalNetworkSetViewerEndpoints, useLocalNetworkUploadCertificate, useLockControlGetStatus, useLockControlLock, useLockControlOpen, useLockControlUnlock, useMediaPlayerGetStatus, useMediaPlayerNext, useMediaPlayerPause, useMediaPlayerPlay, useMediaPlayerPlayMedia, useMediaPlayerPrevious, useMediaPlayerSeek, useMediaPlayerSelectSource, useMediaPlayerSetMute, useMediaPlayerSetRepeat, useMediaPlayerSetShuffle, useMediaPlayerSetVolume, useMediaPlayerStop, useMeshNetworkGetStatus, useMeshNetworkJoin, useMeshNetworkLeave, useMeshNetworkListPeers, useMeshNetworkLogout, useMeshNetworkStartLogin, useMeshNetworkTestConnection, useMetricsProviderCollectSnapshot, useMetricsProviderGetAddonStats, useMetricsProviderGetCached, useMetricsProviderGetCpuTemperature, useMetricsProviderGetCurrent, useMetricsProviderGetDiskSpace, useMetricsProviderGetGpuInfo, useMetricsProviderGetLoadSeries, useMetricsProviderGetProcessStats, useMetricsProviderListAddonInstances, useMetricsProviderListNodeProcesses, useMotionDetectionAnalyze, useMotionDetectionApplyDeviceSettingsPatch, useMotionDetectionGetDeviceLiveContribution, useMotionDetectionGetDeviceSettingsContribution, useMotionDetectionRemoveCamera, useMotionDetectionReset, useMotionGetStatus, useMotionIsDetected, useMotionTriggerGetStatus, useMotionTriggerSetMotionTrigger, useMotionZonesGetOptions, useMotionZonesGetStatus, useMotionZonesSetZone, useMqttBrokerAddBroker, useMqttBrokerGetBrokerConfig, useMqttBrokerGetStatus, useMqttBrokerListBrokers, useMqttBrokerRemoveBroker, useMqttBrokerStartEmbeddedBroker, useMqttBrokerStopEmbeddedBroker, useMqttBrokerTestConnection, useNativeObjectDetectionGetOptions, useNativeObjectDetectionGetStatus, useNativeObjectDetectionSetEnabled, useNativeObjectDetectionSetSettings, useNavigation, useNavigationGetFeatures, useNavigationGetStatus, useNavigationGoToPoint, useNavigationListActions, useNavigationMove, useNavigationPlaySound, useNavigationRunAction, useNavigationSetLightLevel, useNavigationSetLightMode, useNavigationSetLightOn, useNavigationStop, useNetworkAccessGetEndpoint, useNetworkAccessGetStatus, useNetworkAccessListEndpoints, useNetworkAccessStart, useNetworkAccessStop, useNetworkLinkGetStatus, useNetworkQualityGetAllStats, useNetworkQualityGetDeviceStats, useNetworkQualityReportClientStats, useNodesClusterAddonStatus, useNodesDeployAddon, useNodesDirectory, useNodesExecuteQuery, useNodesGetCapUsageGraph, useNodesGetNodeAddons, useNodesRenameNode, useNodesRestartAddon, useNodesRestartNode, useNodesRestartProcess, useNodesSetProcessLogLevel, useNodesShutdownNode, useNodesTopology, useNodesUndeployAddon, useNotificationOutputDeleteTarget, useNotificationOutputDiscoverTargets, useNotificationOutputGetTarget, useNotificationOutputListTargetKinds, useNotificationOutputListTargets, useNotificationOutputProbeTargetConfig, useNotificationOutputSend, useNotificationOutputSetTargetEnabled, useNotificationOutputTestTarget, useNotificationOutputUpsertTarget, useNotificationRulesActivateAlarmProfile, useNotificationRulesCancelSnooze, useNotificationRulesCreateAlarmProfile, useNotificationRulesCreateRule, useNotificationRulesCreateSequence, useNotificationRulesCreateSnooze, useNotificationRulesDeleteAlarmProfile, useNotificationRulesDeleteRule, useNotificationRulesDeleteSequence, useNotificationRulesGetAlarmConfig, useNotificationRulesGetAlarmSensors, useNotificationRulesGetClipRetentionAsks, useNotificationRulesGetConditionCatalog, useNotificationRulesGetHistory, useNotificationRulesGetRule, useNotificationRulesGetRuleLastRuns, useNotificationRulesGetSequence, useNotificationRulesGetTemplateCatalog, useNotificationRulesListDeviceMutes, useNotificationRulesListRules, useNotificationRulesListSequences, useNotificationRulesListSnoozes, useNotificationRulesPreviewStateSummary, useNotificationRulesPreviewTemplate, useNotificationRulesRenameAlarmProfile, useNotificationRulesResolveArtifactUrl, useNotificationRulesSetAlarmConfig, useNotificationRulesSetDeviceMuted, useNotificationRulesSetRuleEnabled, useNotificationRulesTestRule, useNotificationRulesUpdateRule, useNotificationRulesUpdateSequence, useNotifierCancel, useNotifierGetStatus, useNotifierSend, useNumericSensorGetStatus, useOptimisticSlice, useOptionalSystem, useOptionalWidgetRegistry, useOsdGetStatus, useOsdManagerClearSlotBinding, useOsdManagerCopyDeviceConfiguration, useOsdManagerDeleteTemplate, useOsdManagerGetConditionSupport, useOsdManagerGetDeviceOsd, useOsdManagerGetSourceCatalog, useOsdManagerListTemplates, useOsdManagerPreviewSlot, useOsdManagerRenderDevice, useOsdManagerSetSlotBinding, useOsdManagerUpsertTemplate, useOsdSetOverlay, usePTZ, usePetFeederCallPet, usePetFeederCancelFeed, usePetFeederFeed, usePetFeederGetStatus, usePetFeederMarkFoodReplenished, usePetFeederPlaySound, usePetFeederResetDesiccant, usePetFeederSetChildLock, usePetFeederSetFeedSound, usePetFeederSetIndicatorLight, usePetFeederSetVolume, usePickerSiteZones, usePipelineAnalyticsApplyDeviceSettingsPatch, usePipelineAnalyticsCancelRelocateMedia, usePipelineAnalyticsCancelStorageMigrationMove, usePipelineAnalyticsClearTrackRelation, usePipelineAnalyticsClearTracks, usePipelineAnalyticsCompleteRetrainTrack, usePipelineAnalyticsCountRelocatableMedia, usePipelineAnalyticsCountUnstampedEventMedia, usePipelineAnalyticsDeleteDeviceEvents, usePipelineAnalyticsDeleteTracks, usePipelineAnalyticsDeselectRetrainFrame, usePipelineAnalyticsGetActiveTracks, usePipelineAnalyticsGetAudioEvents, usePipelineAnalyticsGetDeviceLiveContribution, usePipelineAnalyticsGetDeviceSettingsContribution, usePipelineAnalyticsGetEventDensity, usePipelineAnalyticsGetEventDensityBatch, usePipelineAnalyticsGetEventMedia, usePipelineAnalyticsGetEventMediaFootprintByKind, usePipelineAnalyticsGetEventStoreFootprint, usePipelineAnalyticsGetKeyEvents, usePipelineAnalyticsGetKeyEventsBatch, usePipelineAnalyticsGetMediaReclaimStatus, usePipelineAnalyticsGetMotionEvents, usePipelineAnalyticsGetObjectEmbeddingRebuildStatus, usePipelineAnalyticsGetObjectEvents, usePipelineAnalyticsGetRetrainExportUrl, usePipelineAnalyticsGetRetrainFrameImage, usePipelineAnalyticsGetSensorEvents, usePipelineAnalyticsGetStorageMigrationMoveStatus, usePipelineAnalyticsGetTrack, usePipelineAnalyticsGetTrackMedia, usePipelineAnalyticsGetTrainingExportSummary, usePipelineAnalyticsGetTrainingExportUrl, usePipelineAnalyticsListArchivedDebugNotes, usePipelineAnalyticsListBirthDecisions, usePipelineAnalyticsListEventKinds, usePipelineAnalyticsListEventKindsBatch, usePipelineAnalyticsListEventMedia, usePipelineAnalyticsListOpsLog, usePipelineAnalyticsListRecentTracks, usePipelineAnalyticsListRelocateMediaJobs, usePipelineAnalyticsListRetrainAnnotations, usePipelineAnalyticsListRetrainFrames, usePipelineAnalyticsListRetrainStaging, usePipelineAnalyticsListTrackMedia, usePipelineAnalyticsListTrackRelations, usePipelineAnalyticsListTracks, usePipelineAnalyticsOwnersWithMedia, usePipelineAnalyticsPauseForStorageMigration, usePipelineAnalyticsProposeRetrainAnnotations, usePipelineAnalyticsPruneEvents, usePipelineAnalyticsPruneEventsBefore, usePipelineAnalyticsPruneTracksBefore, usePipelineAnalyticsReadLongTermSeries, usePipelineAnalyticsRebuildObjectEmbeddings, usePipelineAnalyticsReclaimDebugMedia, usePipelineAnalyticsReconcileFromDisk, usePipelineAnalyticsRefreshStorageLocationsForMigration, usePipelineAnalyticsRelocateMedia, usePipelineAnalyticsRestageRetrainTrack, usePipelineAnalyticsResumeForStorageMigration, usePipelineAnalyticsSaveRetrainAnnotations, usePipelineAnalyticsSearchObjectEvents, usePipelineAnalyticsSelectRetrainFrames, usePipelineAnalyticsSetTrackFlags, usePipelineAnalyticsSetTrackPurity, usePipelineAnalyticsSetTrackRelation, usePipelineAnalyticsStartStorageMigrationMove, usePipelineAnalyticsWipeAllAnalytics, usePipelineAnalyticsWipeObjectEmbeddings, usePipelineExecutorCacheFrameInPool, usePipelineExecutorClearDeviceOverrides, usePipelineExecutorClearModelBoundDeviceOverrides, usePipelineExecutorDeleteModel, usePipelineExecutorDeleteTemplate, usePipelineExecutorDownloadModel, usePipelineExecutorGetAddonModels, usePipelineExecutorGetAudioCapabilities, usePipelineExecutorGetAvailableEngines, usePipelineExecutorGetCapabilities, usePipelineExecutorGetDefaultSteps, usePipelineExecutorGetDetectionConfigSchema, usePipelineExecutorGetEffectiveTuning, usePipelineExecutorGetEngineProvisioning, usePipelineExecutorGetGlobalPipelineConfig, usePipelineExecutorGetGlobalSteps, usePipelineExecutorGetInferenceDeviceHealth, usePipelineExecutorGetOrchestratorConfigSchema, usePipelineExecutorGetReferenceAudio, usePipelineExecutorGetReferenceAudioFiles, usePipelineExecutorGetReferenceImage, usePipelineExecutorGetSchema, usePipelineExecutorGetSelectedEngine, usePipelineExecutorGetVideoPipelineSteps, usePipelineExecutorInferCached, usePipelineExecutorKillEngine, usePipelineExecutorListLoadedEngines, usePipelineExecutorListReferenceImages, usePipelineExecutorListTemplates, usePipelineExecutorRearmInferenceDevice, usePipelineExecutorRunAudioTest, usePipelineExecutorRunPipeline, usePipelineExecutorRunPipelineBatch, usePipelineExecutorSaveTemplate, usePipelineExecutorSetVideoPipelineSteps, usePipelineExecutorSpinEngine, usePipelineExecutorUncacheFrame, usePipelineExecutorUpdateTemplate, usePipelineExecutorValidatePipeline, usePipelineOrchestratorApplyBestSettings, usePipelineOrchestratorApplyDeviceSettingsPatch, usePipelineOrchestratorAssignAudio, usePipelineOrchestratorAssignPipeline, usePipelineOrchestratorDeleteTemplate, usePipelineOrchestratorGetAgentLoad, usePipelineOrchestratorGetAgentSettings, usePipelineOrchestratorGetAudioAssignment, usePipelineOrchestratorGetAudioAssignments, usePipelineOrchestratorGetAudioNodeLoad, usePipelineOrchestratorGetCameraMetrics, usePipelineOrchestratorGetCameraSettings, usePipelineOrchestratorGetCameraStatus, usePipelineOrchestratorGetCameraStatuses, usePipelineOrchestratorGetCameraStepOverrides, usePipelineOrchestratorGetCameraSwitches, usePipelineOrchestratorGetCapabilityBindings, usePipelineOrchestratorGetClusterModelChoice, usePipelineOrchestratorGetDeviceLiveContribution, usePipelineOrchestratorGetDeviceSettingsContribution, usePipelineOrchestratorGetGlobalMetrics, usePipelineOrchestratorGetIngestOwner, usePipelineOrchestratorGetNodeInferenceDevices, usePipelineOrchestratorGetPipelineAssignment, usePipelineOrchestratorGetPipelineAssignments, usePipelineOrchestratorGetPipelineDevicePin, usePipelineOrchestratorGetPlacementStats, usePipelineOrchestratorGetReconcileFromDiskStatus, usePipelineOrchestratorListAgentSettings, usePipelineOrchestratorListTemplates, usePipelineOrchestratorPauseForStorageMigration, usePipelineOrchestratorPlanBestSettings, usePipelineOrchestratorRebalance, usePipelineOrchestratorReconcileFromDisk, usePipelineOrchestratorRemoveAgentSettings, usePipelineOrchestratorRequestOccupancyLook, usePipelineOrchestratorResetNodePipelineDefaults, usePipelineOrchestratorResolvePipeline, usePipelineOrchestratorResumeForStorageMigration, usePipelineOrchestratorSaveTemplate, usePipelineOrchestratorSetAgentCapabilities, usePipelineOrchestratorSetAgentDecodeLimits, usePipelineOrchestratorSetAgentInferenceDevices, usePipelineOrchestratorSetAgentMaxCameras, usePipelineOrchestratorSetAgentReachableHost, usePipelineOrchestratorSetCameraPipelineForAgent, usePipelineOrchestratorSetCameraSensors, usePipelineOrchestratorSetCameraStepOverride, usePipelineOrchestratorSetCameraStepToggle, usePipelineOrchestratorSetCameraSwitch, usePipelineOrchestratorSetCapabilityBinding, usePipelineOrchestratorSetLoadBalancingShares, usePipelineOrchestratorSetPipelineDevicePin, usePipelineOrchestratorSuggestShares, usePipelineOrchestratorUnassignAudio, usePipelineOrchestratorUnassignPipeline, usePipelineOrchestratorUpdateTemplate, usePipelineRunnerAttachCamera, usePipelineRunnerDetachCamera, usePipelineRunnerGetAllCameraMetrics, usePipelineRunnerGetCameraMetrics, usePipelineRunnerGetLocalCameras, usePipelineRunnerGetLocalLoad, usePipelineRunnerGetLocalMetrics, usePipelineRunnerGetNativeCrop, usePipelineRunnerGetParkedTrackFrame, usePipelineRunnerParkTrackFrame, usePipelineRunnerReleaseParkedTrackFrames, usePipelineRunnerReportMotion, usePipelineRunnerRequestOccupancyBurst, usePipelineRunnerRunDetailSubtree, usePipelineRunnerRunStatelessStep, usePlateGalleryAssignPlate, usePlateGalleryAssignPlates, usePlateGalleryCorrectPlateText, usePlateGalleryCreateVehicle, usePlateGalleryDeletePlate, usePlateGalleryDeleteVehicle, usePlateGalleryGetPlateByTrack, usePlateGalleryGetPlateMedia, usePlateGalleryListPlates, usePlateGalleryListVehicleSamples, usePlateGalleryListVehicles, usePlateGalleryRemoveVehicleSample, usePlateGalleryRenameVehicle, usePlateGallerySearchPlates, usePlateGallerySuggestPlateClusters, usePlateGalleryUnassignPlate, usePlateGalleryUnassignPlates, usePlayerOverlayLayer, usePlayerOverlayLayers, usePlayerToolbarButton, usePlayerToolbarButtons, usePowerMeterGetStatus, usePresenceGetStatus, usePressureSensorGetStatus, usePrivacyMaskGetOptions, usePrivacyMaskGetStatus, usePrivacyMaskSetAudioEnabled, usePrivacyMaskSetMask, usePrompt, usePtzAutotrackGetSettings, usePtzAutotrackGetStatus, usePtzAutotrackSetEnabled, usePtzAutotrackSetSettings, usePtzCaptureHomeHere, usePtzContinuousMove, usePtzDeletePreset, usePtzGetHomePreset, usePtzGetHomeReturn, usePtzGetOptions, usePtzGetPosition, usePtzGetPresets, usePtzGetStatus, usePtzGoHome, usePtzGoToPreset, usePtzMove, usePtzSavePreset, usePtzSetAutofocus, usePtzSetHomePreset, usePtzSetHomeReturn, usePtzStop, useRebootReboot, useRecordedPlayback, useRecordingArchiveApplyDeviceSettingsPatch, useRecordingArchiveCancelRelocateJob, useRecordingArchiveCancelStorageMigrationMove, useRecordingArchiveDeleteFootprint, useRecordingArchiveGetAvailabilityBatch, useRecordingArchiveGetDaysWithRecordingsBatch, useRecordingArchiveGetDeviceConfig, useRecordingArchiveGetDeviceLiveContribution, useRecordingArchiveGetDeviceSettingsContribution, useRecordingArchiveGetPlacement, useRecordingArchiveGetRelocateResidue, useRecordingArchiveGetStatus, useRecordingArchiveGetStorageMigrationMoveStatus, useRecordingArchiveGetStorageUsage, useRecordingArchiveListOpsLog, useRecordingArchiveListRelocateJobs, useRecordingArchiveLocateSegment, useRecordingArchivePauseForStorageMigration, useRecordingArchivePlanStorageRebalance, useRecordingArchivePruneFootage, useRecordingArchiveReadGopBytes, useRecordingArchiveReadSegmentBytes, useRecordingArchiveReadWindowBytes, useRecordingArchiveReconcileLedgerAgainstDisk, useRecordingArchiveRefreshStorageLocationsForMigration, useRecordingArchiveRelocateFootage, useRecordingArchiveRenderClip, useRecordingArchiveRenderGif, useRecordingArchiveRescanStorage, useRecordingArchiveResumeForStorageMigration, useRecordingArchiveSetDeviceConfig, useRecordingArchiveSetDevicePlacement, useRecordingArchiveStartStorageMigrationMove, useRecordingArchiveStartStorageRebalance, useRecordingExportCancelExport, useRecordingExportCreateExport, useRecordingExportDeleteExport, useRecordingExportGetDownloadUrl, useRecordingExportGetExport, useRecordingExportListExports, useRecordingExportReadExportBytes, useRecordingGetAvailability, useRecordingGetDaysWithRecordings, useRecordingGetPlayback, useRecordingGetPlaybackOptions, useRecordingListSources, useRecordingOnboardGetOptions, useRecordingOnboardGetStatus, useRecordingOnboardSetSettings, useRecordingSignalGetStatus, useRemoteComponent, useRoleDisplayDefaults, useSceneMonitorCaptureReference, useSceneMonitorCreateScene, useSceneMonitorDeleteReference, useSceneMonitorDeleteScene, useSceneMonitorGetStatus, useSceneMonitorListScenes, useSceneMonitorListScenesBatch, useSceneMonitorRecheckNow, useSceneMonitorResetScene, useSceneMonitorUpdateScene, useScriptRunnerGetStatus, useScriptRunnerRun, useScriptRunnerStop, useScrubController, useServerManagementApplyServerUpdate, useServerManagementCheckServerUpdate, useServerManagementGetServerPackageStatus, useServerManagementRestartServer, useServerManagementRollbackServerUpdate, useSettingsStoreAggregate, useSettingsStoreCount, useSettingsStoreDeclareCollection, useSettingsStoreDelete, useSettingsStoreDeleteWhere, useSettingsStoreGet, useSettingsStoreHistogram, useSettingsStoreInsert, useSettingsStoreInsertMany, useSettingsStoreIsEmpty, useSettingsStoreQuery, useSettingsStoreSet, useSettingsStoreUpdate, useSettingsStoreUpdateWhere, useSettingsStoreUpsert, useSmokeGetStatus, useSnapshotApplyDeviceSettingsPatch, useSnapshotGetDebugState, useSnapshotGetDeviceLiveContribution, useSnapshotGetDeviceSettingsContribution, useSnapshotGetSnapshot, useSnapshotGetSnapshotLinks, useSnapshotGetSnapshotOverview, useSnapshotGetStatus, useSnapshotInvalidateCache, useStationaryOverlay, useStorageAbortUpload, useStorageBeginDownload, useStorageBeginUpload, useStorageDelete, useStorageDeleteLocation, useStorageEndDownload, useStorageExists, useStorageFinalizeUpload, useStorageGetAvailableSpace, useStorageList, useStorageListDrainProgress, useStorageListLocationDeclarations, useStorageListLocations, useStorageListProviders, useStorageMigrationCancel, useStorageMigrationCleanupCancel, useStorageMigrationCleanupStart, useStorageMigrationCleanupStatus, useStorageMigrationDrain, useStorageMigrationHistory, useStorageMigrationMovers, useStorageMigrationPlan, useStorageMigrationResidue, useStorageMigrationStart, useStorageMigrationStatus, useStorageRead, useStorageReadChunk, useStorageResolve, useStorageTestConfig, useStorageTestLocation, useStorageUpsertLocation, useStorageWrite, useStorageWriteChunk, useStoredGroupBy, useStreamBrokerAcquireEgressTranscode, useStreamBrokerApplyDeviceSettingsPatch, useStreamBrokerAssignProfile, useStreamBrokerFetchEventMedia, useStreamBrokerForgetDeviceHardware, useStreamBrokerGetAllRtspEntries, useStreamBrokerGetBrokerStats, useStreamBrokerGetDeviceAudioMute, useStreamBrokerGetDeviceLiveContribution, useStreamBrokerGetDeviceSettingsContribution, useStreamBrokerGetPreBufferInfo, useStreamBrokerGetRtspEntry, useStreamBrokerGetRtspPort, useStreamBrokerGetStreamUrl, useStreamBrokerGetStreamWithCodec, useStreamBrokerIsRtspEnabled, useStreamBrokerKillClient, useStreamBrokerListAllCameraStreams, useStreamBrokerListAllProfileSlots, useStreamBrokerListClients, useStreamBrokerProbeStream, useStreamBrokerProduceEventMedia, useStreamBrokerPublishCameraStream, useStreamBrokerPullAudioChunks, useStreamBrokerPullFrameHandles, useStreamBrokerRegenerateRtspToken, useStreamBrokerReleaseEgressTranscode, useStreamBrokerReleaseStreamWithCodec, useStreamBrokerRenderPreBufferClip, useStreamBrokerReportStreamSignal, useStreamBrokerRestartProfile, useStreamBrokerRetractCameraStream, useStreamBrokerSetDeviceAudioMute, useStreamBrokerSetPreBufferDuration, useStreamBrokerSetRtspEnabled, useStreamBrokerSubscribeAudioChunks, useStreamBrokerSubscribeFrames, useStreamBrokerUnassignProfile, useStreamBrokerUnsubscribeAudioChunks, useStreamBrokerUnsubscribeFrames, useStreamCatalogGetCatalog, useStreamParamsGetConfigSchema, useStreamParamsGetOptions, useStreamParamsGetStatus, useStreamParamsSetProfile, useStreamSignalsListSignals, useSwitchGetStatus, useSwitchSetState, useSystem, useSystemDetectSiteLocation, useSystemFeatureFlags, useSystemForceRetentionCleanup, useSystemGetFailureContributions, useSystemGetLoadContributions, useSystemGetLoggingSettings, useSystemGetRequestCensus, useSystemGetRetentionConfig, useSystemGetSiteLocation, useSystemHealth, useSystemInfo, useSystemMutation, useSystemNetworkAddresses, useSystemQuery, useSystemSetLoggingSettings, useSystemSetRetentionConfig, useSystemSetSiteLocation, useTamperGetStatus, useTemperatureSensorGetStatus, useTerminalSessionAdoptLegacyMonitor, useTerminalSessionClose, useTerminalSessionCreateInstance, useTerminalSessionDeleteInstance, useTerminalSessionListInstances, useTerminalSessionListLegacyCameras, useTerminalSessionListProfiles, useTerminalSessionListSessions, useTerminalSessionOpenSession, useTerminalSessionPullOutput, useTerminalSessionResize, useTerminalSessionSetInstanceEnabled, useTerminalSessionUpdateInstance, useTerminalSessionWriteInput, useThemeMode, useToastOnToast, useTurnProviderGetTurnServers, useUpdateGetStatus, useUpdateInstallUpdate, useUrlQueryParam, useUserManagementConfirmTotp, useUserManagementCreateApiKey, useUserManagementCreateScopedToken, useUserManagementCreateUser, useUserManagementDeleteUser, useUserManagementDisableTotp, useUserManagementGetTotpStatus, useUserManagementListApiKeys, useUserManagementListOauthSessions, useUserManagementListScopedTokens, useUserManagementListUsers, useUserManagementOauthExchangeCode, useUserManagementOauthIssueCode, useUserManagementOauthRefresh, useUserManagementOauthVerifyAccessToken, useUserManagementResetPassword, useUserManagementRevokeApiKey, useUserManagementRevokeOauthSession, useUserManagementRevokeScopedToken, useUserManagementSetUserScopes, useUserManagementSetupTotp, useUserManagementUpdateUser, useUserManagementValidateApiKey, useUserManagementValidateCredentials, useUserManagementValidateScopedToken, useUserManagementVerifyTotp, useVacuumControlGetStatus, useVacuumControlLocate, useVacuumControlPause, useVacuumControlReturnToBase, useVacuumControlSetFanSpeed, useVacuumControlStart, useVacuumControlStop, useValveClose, useValveGetStatus, useValveOpen, useValveSetPosition, useValveStop, useVibrationGetStatus, useVideoclipsDialClipStream, useVideoclipsGetClipPlayback, useVideoclipsGetPlaybackOptions, useVideoclipsListClips, useVideoclipsListSources, useVideoclipsOfferClipBytes, useVideoclipsReadClipBytes, useVodPlayback, useWaterHeaterGetStatus, useWaterHeaterSetAway, useWaterHeaterSetOperationMode, useWaterHeaterSetTargetTemp, useWeatherGetStatus, useWebrtcSessionAddIceCandidate, useWebrtcSessionApplyDownlinkProbe, useWebrtcSessionCloseSession, useWebrtcSessionCreateSession, useWebrtcSessionGetIceCandidates, useWebrtcSessionGetSessionDebug, useWebrtcSessionGetSessionState, useWebrtcSessionHandleAnswer, useWebrtcSessionHandleOffer, useWebrtcSessionHasAdaptiveBitrate, useWebrtcSessionListStreams, useWidget, useWidgetMetadata, useWidgetRegistry, useZoneAnalyticsGetCameraHistory, useZoneAnalyticsGetCurrentSnapshot, useZoneAnalyticsGetCurrentSnapshotBatch, useZoneAnalyticsGetUnzonedHistory, useZoneAnalyticsGetZoneHistory, useZoneEditing, useZoneRulesListRules, useZoneRulesSetRules, useZonesAddZone, useZonesListZones, useZonesRemoveZone, useZonesUpdateZone, vacuumStateMeta, validateScopes, valveStateMeta, variantLabel, waterHeaterPhase, waterHeaterTint, weatherConditionMeta, weatherTint, withResolvedParentDetail, writeStoredGroupBy };
61010
+ export { AddonGlobalSettingsForm, AdminLinkQuickAction, AgentStepEditor, AlarmHeroCard, AlarmInlineControl as AlarmPanelInlineControl, AppShell, ArcKnob, AudioClassificationList, AudioLevelWaveform, AudioWaveform, AutomationHeroCard, AutomationInlineControl, AutotrackSection, BTN_COMPACT, BTN_COMPACT_DANGER, BTN_COMPACT_PRIMARY, BTN_COMPACT_WARNING, Badge, BatteryBadge, BottomSheet, Breadcrumb, BrightnessPanel, Button, ButtonControl, ButtonHeroCard, CARD_MODE_MIN_COLUMNS, CENTER, CHIP_ACTIVE, CHIP_BASE, CHIP_INACTIVE, CLASS_COLORS, COLUMN_BREAKPOINT_CLASS, COLUMN_PRIORITY, COMMIT_DEDUPE_TOLERANCE_MS, COMMIT_DEDUPE_WINDOW_MS, CONTROL_CAP_NAMES, CONTROL_FILLS, CameraStreamPlayer, Card, Checkbox, ChildSectionAccordion, ClimatePanel, CodeBlock, CollapsibleCard, ConfigFormBuilder, FormField as ConfigFormField, ConfigNotificationTargetMultiSelectFieldView, ConfigNotificationTargetSelectFieldView, ConfigSchemaField, ConfirmActionButton, ConfirmDialogProvider, ConsumablesPanel, ContainerChildrenProvider, ContainerPrimaryHero, ControlColumn, ControlHeroCard, ControlInlineControl, ControlPanel, CopyButton, CoverHeroCard, CoverInlineControl, CoverPanel, CustomFieldRenderersProvider, DEFAULT_COLOR, DEVICE_COLUMNS, DEVICE_LIST_PAGE_SIZE_KEY, DEVICE_LIST_PAGE_SIZE_OPTIONS, DEVICE_PICKER_GROUP_STORAGE_KEY, DEVICE_ROLE_META, DEVICE_TYPE_CONTROL, DEVICE_TYPE_META, DISPLAY_ICON_REGISTRY, DataTable, DataTableFilterBar, DetectionCanvas, DetectionOverlay, DetectionResultTree, DevShell, DeviceActivityPanel, DeviceBatchToolbar, DeviceCard, DeviceContextProvider, DeviceExportPanel, DeviceFilterBar, DeviceGrid, DeviceItem, DeviceList, DeviceMultiSelectField, DevicePicker, DeviceQuickDetailProvider, DeviceSelectField, DeviceSelectorPicker, DeviceStepMatrix, DevicesFailedLine, DevicesLoadingLine, DevicesWidenWarning, Dialog, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, DiscoveryPanel, DoorbellRecentPanel, Dropdown, DropdownContent, DropdownItem, DropdownTrigger, DummyHeroCard, DummyInline, EMPTY_DATA_TABLE_FILTER, EVENT_KIND_ICONS, EmptyState, EndpointResultsCard, EndpointTestCard, ErrorBox, EventKindGlyph, EventStream, ExtensionsPanel, FILL, FLEET_LIST_GC_TIME_MS, FLEET_LIST_QUERY_KEY, FLEET_LIST_STALE_TIME_MS, FanHeroCard, FanInlineControl, FanPanel, FilterBar, FloatingEventStream, FloatingLogStream, FloatingPanel, FormField$1 as FormField, GRID_GAP, GRID_PAIRED, GRID_QUICK_STATS, GripTrack, GroupedModelSelector, HOST_WIDGETS, HUB_SESSION_RUN_MS, HlsVideo, HoverZoomImage, HubSessionTestCard, HumidifierHeroCard, HumidifierInlineControl, INPUT_COMPACT, INPUT_SHELL, IconAction, IconButton, IconPicker, ImageHeroCard, ImageInlineControl, ImageSelector, InferenceConfigSelector, Input, KebabMenu, KeyValueList, LIST_ROW, Label, LawnMowerHeroCard, LawnMowerInlineControl, LightHeroCard, LightInlineControl, LockHeroCard, LockInlineControl, LockPanel, LogStream, LoginForm, MODE_COLOR, MaskShapeCanvas, MediaPlayerHeroCard, MediaPlayerInlineControl, MediaPlayerPanel, MobileDrawer, ModelPicker, MotionZonesSettings, NAVIGATION_DRIVE_INTERVAL_MS, NatResultCard, NavigationPanel, NetworkLinkBadge, NodeMultiSelectField, NodePicker, NodeSelectField, NotificationTargetSelectField, NotificationTargetsField, OPTION_SELECT_SEARCH_MIN_OPTIONS, OfflineBadge, OptionSelect, PHASE_CONFIG, PICKER_CHIP_LIMIT, PICKER_DEFAULT_GROUP_BY, PICKER_GROUP_BY_MODES, PICKER_GROUP_COLLAPSE_AT, PICKER_NONE_KEY, PICKER_NOUN_PLURAL, PICKER_NO_FILTERS, PICKER_PAGE_SIZE, PICKER_SEARCH_DEBOUNCE_MS, PICKER_WINDOW, PRIORITY, PTZOverlay, PageHeader, PhaseIcon, PipelineBuilder, PipelineRuntimeSelector, PipelineStep, PipelineTreeMatrix, PlaceholderChipField, PlayerOverlaysProvider, Popover, PopoverContent, PopoverRowAction, PopoverTrigger, PrimaryChildPicker, PrivacyMaskSettings, ProviderBadge, PtzPanel, QrCode, RECONNECT_POLICY, RECORDED_PLAYBACK_MODES, RIGHT, ROI_AREA_WARN_FRACTION, ROLE_DESCRIPTOR, RadialGauge, RecordedPlaybackProvider, RecordingPanel, ResponseLog, RoleDisplayDefaultsFromServer, RoleDisplayDefaultsProvider, RunReport, SCENE_CAPTURE_VARIANTS, SECTION_BODY, SECTION_CARD, SECTION_HEADER, SETTING_ROW, SETTING_ROW_LABEL, SETTING_ROW_STACK_BREAKPOINT, SETTING_ROW_VALUE, SETTING_ROW_VALUE_TEXT, SNAPSHOT_MEDIA_PATH, SPLIT_PANEL_OUTER, SPLIT_PANEL_SIDE, STACK_GAP, STATE_COLOR, SceneMonitorEditor, ScopePicker, ScriptHeroCard, ScriptInlineControl, ScrollArea, SectionCard, Select, SelectedChip, SemanticBadge, SensorHeroCard, SensorInlineControl, SensorValueAtom, Separator, SettingRow, Sidebar, SidebarItem, Skeleton, SlideOverPanel, SlideToggle, SnapshotButton, SortHeaderButton, StatCard, StateValuesStream, StatusBadge, StepTimings, StepTreeMaster, Stepper, StreamBrokerSelector, StreamDebugOverlay, StreamPanel, Switch, SwitchHeroCard, SwitchInlineControl, SwitchPanel, SystemProvider, TABLE_SCROLL_MAX_H, TEXT_FIELD_LABEL, TEXT_HINT, TEXT_METRIC, TEXT_SECTION_LABEL, TEXT_VALUE, TIMEZONES, Tabs, TabsContent, TabsList, TabsTrigger, TapToggle, ThemeProvider, ThermostatHeroCard, ThermostatInlineControl, TimezoneSelector, Tooltip, TooltipContent, TooltipTrigger, UnitPicker, VacuumHeroCard, VacuumInlineControl, ValueReadout, ValveHeroCard, ValveInlineControl, VersionBadge, VodPlaybackProvider, WaterHeaterHeroCard, WaterHeaterInlineControl, WeatherHeroCard, WeatherInlineControl, WidgetMetricCard, WidgetPanel, WidgetRegistryProvider, WidgetSlot, ZoneEditingProvider, MicOff as a, agentColumnKey, allDeviceTypeFilterOptions, applyFleetQueryDefaults, applyPickerFilters, ariaSortForColumn, badgeFor, buildPeerConnectionConfig, buildStepTreeFromSchema, EyeOff as c, catalogHasDraft, childEntityId, childListName, chipOverflow, classifyAddressScope, classifyCapability, classifySessionVerdict, clearPickerGroup, cn, columnsForContext, compareSrflxMappings, containerChildToRef, countableDevices, coverHighlight, coverageLine, createSharedContext, createTheme, cursorFractionFor, darkColors, dataTableFacets, defaultTheme, deriveDeviceKind, deriveWebrtcDebugStats, describeDeviceSelector, deviceIntegrationResolver, deviceListStateOf, deviceMatchesFilter, deviceOptionLabel, deviceRoleMeta, deviceRoleMetaOf, deviceTypeLabel, deviceTypeMeta, deviceTypeMetaOf, devicesToOptions, draftFromQuery, emptyDevicesWidens, emptySessionRun, ensureMfHostInit, eventKindLabel, filterDataTableRows, filterDeviceOptions, filterOptions as filterOptionSelectOptions, filterPickerDevices, finalizeSessionRun, findTimezone, flattenPickerGrouping, flattenPickerRows, fleetViewOf, formatControlDateTime, formatLastSeen, formatNumeric, freezeVerdict, fuzzyMatch, getClassColor, getPhaseVisual, groupAgentColumns, groupChildrenByLayout, groupPickerDevices, groupPickerDevicesBy, hardwareLabel, hubOrigin, hubPath, hubUrl, hubWsUrl, humanDuration, humidifierTint, Mic as i, initialScrubState, insertAtSelection, isAbsentProvider, isDataTableFilterActive, isFieldVisible, isGroupExpanded, Camera as l, lawnMowerActivityMeta, lightColors, loadRemoteBundle, makeScrubBridge, metadataEntries, metadataString, mirror, mountAddonPage, mountPath, Square as n, nextReconnectAction, nextSort, nextSortDirection, nextTableSort, normalizeForSearch, ImageOff as o, offListSelections, openTopLayer, optionSearchText as optionSelectSearchText, orphanDeviceLabel, overrideEntityIdFromLink, pageRows, parentDetailOf, parseIceCandidate, parsePickerGroupBy, parseRecordedServerMessage, partitionSelected as partitionOptionSelectSelected, partitionSuggested, pickerFacetFilterShown, pickerFacetKey, pickerFacetOptions, pickerFiltersActive, pickerGroupKey, pickerHiddenSelectedCount, pickerLocationKey, pickerQueryTokens, pickerRowFacts, pickerSearchText, pickerZoneFilterSuspended, pinnedStreamChoices, providerIcons, RotateCcw as r, readStoredGroupBy, reduceSessionRun, resetHubMountForTests, resetPickerFilters, resolveContainerPrimary, resolveControlAlign, resolveDeviceControl, resolveDeviceIntegration, resolveDisplayIcon, resolveEffectiveDefaultModel, resolveEffectiveStepModel, resolveEventKindIcon, resolveSearchable as resolveOptionSelectSearchable, resolveParentNames, resolvePrimaryChild, resolveSelectedDevices, resolveSensorDisplay, resolveStepDefaultModel, resolveTableLayout, roiTooLarge, rootSourceState, routerBasename, Grid2x2 as s, sameLocationSuggestions, scrubReducer, selectPickerGroup, selectedDeviceOptions, serializeRecordedCommand, shouldCommit, shouldEmit, shouldShowAddNewFooter, shouldShowUseQueryRow, shouldUseSingleNode, showsNoCamerasHint, sortRows, sortRowsByColumn, statusIcons, statusLine, stepHasModelForFormat, stepModelOptions, stripParentNamePrefix, summarizeHubCandidates, Trash2 as t, tabForSelector, tankAlert, themeToCss, toStationaryOverlayBox, toggleDataTableFacet, togglePickerDevice, togglePickerKey, trpc, typeLabel, createLucideIcon as u, useAccessoriesGetStatus, useAccessoriesSetChildHidden, useAddonPagesListPages, useAddonSettingsGetDeviceSettings, useAddonSettingsGetGlobalSettings, useAddonSettingsGetIntegrationSettings, useAddonSettingsUpdateDeviceSettings, useAddonSettingsUpdateGlobalSettings, useAddonWidgetsListWidgets, useAddonsApplyAutoUpdateToAll, useAddonsCancelJob, useAddonsCustom, useAddonsForceRefresh, useAddonsGetAddonAutoUpdate, useAddonsGetAutoUpdateSettings, useAddonsGetJob, useAddonsGetLastRestart, useAddonsGetLogs, useAddonsGetVersions, useAddonsInstallFromWorkspace, useAddonsInstallPackage, useAddonsIsWorkspaceAvailable, useAddonsList, useAddonsListCapabilityProviders, useAddonsListFrameworkPackages, useAddonsListJobs, useAddonsListPackages, useAddonsListUpdates, useAddonsListWorkspacePackages, useAddonsOnAddonLogs, useAddonsReloadPackages, useAddonsRestartAddon, useAddonsRestartServer, useAddonsRetryLoad, useAddonsRollbackPackage, useAddonsSearchAvailable, useAddonsSetAddonAutoUpdate, useAddonsSetAutoUpdateSettings, useAddonsSetCapabilityProviderEnabled, useAddonsStartJob, useAddonsUninstallPackage, useAddonsUpdatePackage, useAirQualitySensorGetStatus, useAlarmPanelArm, useAlarmPanelDisarm, useAlarmPanelGetStatus, useAlarmPanelTrigger, useAlert, useAlertsDismiss, useAlertsEmit, useAlertsGetUnreadCount, useAlertsList, useAlertsMarkAllRead, useAlertsMarkRead, useAlertsUpdate, useAllWidgets, useAmbientLightSensorGetStatus, useAudioAnalysisApplyDeviceSettingsPatch, useAudioAnalysisGetDeviceLiveContribution, useAudioAnalysisGetDeviceSettingsContribution, useAudioAnalysisResolveDeviceSettings, useAudioAnalyzerAnalyseChunk, useAudioAnalyzerAttachDevice, useAudioAnalyzerClassify, useAudioAnalyzerDetachDevice, useAudioAnalyzerDispose, useAudioAnalyzerIsReady, useAudioAnalyzerReprobeAudioEngine, useAudioCodecCanHandle, useAudioCodecCloseSession, useAudioCodecCreateDecodeSession, useAudioCodecCreateEncodeSession, useAudioCodecFlushEncode, useAudioCodecListActiveSessions, useAudioCodecListSupportedCodecs, useAudioCodecPullEncoded, useAudioCodecPullPcm, useAudioCodecPushEncodedFrame, useAudioCodecPushPcm, useAudioMetricsGetCurrentSnapshot, useAudioMetricsGetHistory, useAutomationControlDisable, useAutomationControlEnable, useAutomationControlGetStatus, useAutomationControlTrigger, useBackupCancel, useBackupDelete, useBackupDeleteSchedule, useBackupGetEntries, useBackupList, useBackupListArchives, useBackupListDestinations, useBackupListLocations, useBackupListRuns, useBackupListSchedules, useBackupPreviewSchedule, useBackupRestore, useBackupTrigger, useBackupUpsertDestinationPolicy, useBackupUpsertSchedule, useBatteryGetStatus, useBatteryWakeForStream, useBinaryGetStatus, useBrightnessGetStatus, useBrightnessSetBrightness, useBrokerAdd, useBrokerGet, useBrokerGetBrokerConfig, useBrokerGetSettings, useBrokerGetSettingsSchema, useBrokerGetState, useBrokerGetStatus, useBrokerList, useBrokerListProviders, useBrokerPublish, useBrokerRemove, useBrokerSetSettings, useBrokerSubscribe, useBrokerTestConnection, useBrokerTestSettings, useBrokerUnsubscribe, useButtonPress, useCalendarsGetPreferences, useCalendarsListCalendars, useCalendarsListEvents, useCalendarsSetCalendarPreference, useCameraCredentialsGetCredentials, useCameraCredentialsGetStatus, useCameraGridLayoutApplyDeviceSettingsPatch, useCameraGridLayoutGetDeviceLiveContribution, useCameraGridLayoutGetDeviceSettingsContribution, useCameraGridLayoutGetLayout, useCameraGridLayoutSaveLayout, useCameraPipelineConfigApplyDeviceSettingsPatch, useCameraPipelineConfigGetDeviceLiveContribution, useCameraPipelineConfigGetDeviceSettingsContribution, useCameraStreamsGetBrokerStreams, useCameraStreamsGetCameraStreams, useCameraStreamsGetProfileRtspEntries, useCameraStreamsGetRtspEntries, useCameraStreamsPickStream, useCapabilityPresence, useCarbonMonoxideGetStatus, useClimateControlGetStatus, useClimateControlSetFanMode, useClimateControlSetMode, useClimateControlSetPreset, useClimateControlSetSwingHorizontal, useClimateControlSetSwingVertical, useClimateControlSetTarget, useClimateControlSetTargetHumidity, useClimateControlSetTargetRange, useClusterNodes, useClusterTopology, useColorGetStatus, useColorSetColor, useConfirm, useConnectionTestDescribeTest, useConnectionTestTestSettings, useConnectivityGetStatus, useConsumablesGetStatus, useConsumablesReset, useContactGetStatus, useContainerChildren, useControlGetStatus, useControlSetValue, useCoreBlocksCompile, useCoreBlocksCreate, useCoreBlocksCreateComposition, useCoreBlocksDelete, useCoreBlocksGet, useCoreBlocksGetBlockSettings, useCoreBlocksGetComposition, useCoreBlocksGetCustomization, useCoreBlocksGetTypeDefs, useCoreBlocksList, useCoreBlocksListComposableCaps, useCoreBlocksListCompositions, useCoreBlocksRestart, useCoreBlocksSetEnabled, useCoreBlocksUpdate, useCoreBlocksUpdateBlockSettings, useCoreBlocksUpdateComposition, useCoreBlocksValidateComposition, useCoverClose, useCoverGetStatus, useCoverOpen, useCoverSetPosition, useCoverSetTiltPosition, useCoverStop, useCustomFieldRenderer, useDataTableFilter, useDayNightGetOptions, useDayNightGetStatus, useDayNightSetSettings, useDebouncedString, useDecoderCreateSession, useDecoderDestroySession, useDecoderGetFrame, useDecoderGetInfo, useDecoderGetShmStats, useDecoderGetStats, useDecoderListActiveSessions, useDecoderOpenStream, useDecoderPullFrames, useDecoderPullHandles, useDecoderPushPacket, useDecoderReprobeHwaccel, useDecoderSupportsCodec, useDecoderUpdateConfig, useDetectionPipelineApplyDeviceSettingsPatch, useDetectionPipelineGetDeviceLiveContribution, useDetectionPipelineGetDeviceSettingsContribution, useDevShell, useDevice, useDeviceAdminLink, useDeviceAdminLinkGetAdminLink, useDeviceAdoptionAdopt, useDeviceAdoptionGetCandidate, useDeviceAdoptionGetStatus, useDeviceAdoptionListCandidateFilters, useDeviceAdoptionListCandidates, useDeviceAdoptionRefresh, useDeviceAdoptionRelease, useDeviceAdoptionResync, useDeviceAutotrack, useDeviceBattery, useDeviceCapSlice, useDeviceCapability, useDeviceDetections, useDeviceDiscoveryAdoptDevice, useDeviceDiscoveryGetStatus, useDeviceDiscoveryListDiscovered, useDeviceDiscoveryRefreshDiscovery, useDeviceDiscoveryReleaseDevice, useDeviceExportApplyDeviceSettingsPatch, useDeviceExportExposeDevice, useDeviceExportGetDeviceLiveContribution, useDeviceExportGetDeviceSettingsContribution, useDeviceExportGetStatus, useDeviceExportListExposedDevices, useDeviceExportListSupportedDeviceKinds, useDeviceExportUnexposeDevice, useDeviceExtensionListCandidates, useDeviceExtensionListSlots, useDeviceExtensionListSources, useDeviceExtensionSetSlot, useDeviceId, useDeviceListPageSize, useDeviceManagerAddLocation, useDeviceManagerAdoptDevice, useDeviceManagerAdoptionAdopt, useDeviceManagerAdoptionCancelJob, useDeviceManagerAdoptionListCandidateFilters, useDeviceManagerAdoptionListCandidates, useDeviceManagerAdoptionListJobs, useDeviceManagerAdoptionRefresh, useDeviceManagerAdoptionRelease, useDeviceManagerAdoptionResync, useDeviceManagerAdoptionStartJob, useDeviceManagerAllocateDeviceId, useDeviceManagerApplyInitialMeta, useDeviceManagerCreateDevice, useDeviceManagerDisable, useDeviceManagerDiscoverAllProviders, useDeviceManagerDiscoverDevices, useDeviceManagerDiscoverProvider, useDeviceManagerDiscoveryProviders, useDeviceManagerEnable, useDeviceManagerGetAllBindings, useDeviceManagerGetBindings, useDeviceManagerGetBindingsBatch, useDeviceManagerGetChildren, useDeviceManagerGetChildrenBatch, useDeviceManagerGetConfigSchema, useDeviceManagerGetCreationSchema, useDeviceManagerGetDevice, useDeviceManagerGetDeviceAggregate, useDeviceManagerGetDeviceLiveInfoAggregate, useDeviceManagerGetDeviceSettingsAggregate, useDeviceManagerGetDeviceStatusAggregate, useDeviceManagerGetDeviceStatusAggregateBatch, useDeviceManagerGetLinkedDevices, useDeviceManagerGetLinkedDevicesBatch, useDeviceManagerGetRoleDisplayDefaults, useDeviceManagerGetSettingsSchema, useDeviceManagerGetStreamProfileMap, useDeviceManagerGetStreamSources, useDeviceManagerGetWireableFields, useDeviceManagerGetWrapperActivation, useDeviceManagerListAll, useDeviceManagerListBindableCapsForDeviceType, useDeviceManagerListDeviceIdsByCaps, useDeviceManagerListLocationDescriptions, useDeviceManagerListLocationIcons, useDeviceManagerListLocations, useDeviceManagerListPersistedByAddon, useDeviceManagerListSiteZones, useDeviceManagerListWrappersForCap, useDeviceManagerLoadConfig, useDeviceManagerLoadMeta, useDeviceManagerLoadRuntimeState, useDeviceManagerMigrateDevice, useDeviceManagerPersistConfig, useDeviceManagerProbeStreams, useDeviceManagerProviderCreationType, useDeviceManagerProviderDiscoveryParamsSchema, useDeviceManagerRegisterDevice, useDeviceManagerRemove, useDeviceManagerRemoveByIntegration, useDeviceManagerRemoveDevice, useDeviceManagerRemoveLocation, useDeviceManagerRemoveSiteZone, useDeviceManagerRenameLocation, useDeviceManagerRenameSiteZone, useDeviceManagerRunDeviceAction, useDeviceManagerSetChildLayout, useDeviceManagerSetDisabled, useDeviceManagerSetDisplay, useDeviceManagerSetIntegrationId, useDeviceManagerSetLinkDeviceId, useDeviceManagerSetLocation, useDeviceManagerSetLocationDescription, useDeviceManagerSetLocationIcon, useDeviceManagerSetLocationZone, useDeviceManagerSetMetadata, useDeviceManagerSetName, useDeviceManagerSetPrimaryChildEntityId, useDeviceManagerSetRole, useDeviceManagerSetRoleDisplayDefaults, useDeviceManagerSetSiteZone, useDeviceManagerSetStreamProfileMap, useDeviceManagerSetType, useDeviceManagerSetWrapperActive, useDeviceManagerTestCreationField, useDeviceManagerTestField, useDeviceManagerUpdateConfig, useDeviceManagerUpdateDeviceField, useDeviceManagerUpdateDeviceFieldsBatch, useDeviceNetworkLink, useDeviceOpsGetConfigEntries, useDeviceOpsGetRawState, useDeviceOpsGetSettingsSchema, useDeviceOpsGetStreamSources, useDeviceOpsRemoveDevice, useDeviceOpsRunAction, useDeviceOpsSetConfig, useDeviceProviderAdoptDiscoveredDevice, useDeviceProviderCreateDevice, useDeviceProviderDiscoverDevices, useDeviceProviderGetChildCreationSchema, useDeviceProviderGetDevices, useDeviceProviderGetDiscoveryParamsSchema, useDeviceProviderGetManualCreationType, useDeviceProviderGetStatus, useDeviceProviderReloadDevice, useDeviceProviderStart, useDeviceProviderStop, useDeviceProviderSupportsDiscovery, useDeviceProviderSupportsManualCreation, useDeviceProviderTestCreationField, useDeviceProxy, useDeviceQuickDetailOpen, useDeviceSnapshot, useDeviceSnapshotImage, useDeviceState, useDeviceStateClaimFields, useDeviceStateGetAllSnapshots, useDeviceStateGetCapSlice, useDeviceStateGetNativeCapSlice, useDeviceStateGetSnapshot, useDeviceStateListClaims, useDeviceStatePatchOwnedFields, useDeviceStateReleaseClaim, useDeviceStateSetCapSlice, useDeviceStateSlice, useDeviceStatusGetStatus, useDeviceWebrtc, useDevices, useDoorbellEvents, useDoorbellGetStatus, useEnumSensorGetStatus, useEventEmitterGetStatus, useEventInvalidation, useEventStreamLatest, useEventStreamMap, useFaceGalleryAssignFace, useFaceGalleryAssignFaces, useFaceGalleryAuditIdentitySamples, useFaceGalleryCreateIdentity, useFaceGalleryDeleteFace, useFaceGalleryDeleteIdentity, useFaceGalleryGetFaceByTrack, useFaceGalleryGetFaceMedia, useFaceGalleryListIdentities, useFaceGalleryListIdentitySamples, useFaceGalleryListRecentFaces, useFaceGalleryRemoveSample, useFaceGalleryRenameIdentity, useFaceGallerySuggestFaceClusters, useFaceGalleryUnassignFace, useFaceGalleryUnassignFaces, useFanControlGetStatus, useFanControlSetDirection, useFanControlSetOscillating, useFanControlSetPercentage, useFanControlSetPreset, useFeatureProbeGetStatus, useFloodGetStatus, useGasGetStatus, useHumidifierGetStatus, useHumidifierSetMode, useHumidifierSetOn, useHumidifierSetTargetHumidity, useHumiditySensorGetStatus, useImageGetStatus, useImageSettingsGetOptions, useImageSettingsGetStatus, useImageSettingsSetSettings, useIntegrationsCreate, useIntegrationsDelete, useIntegrationsGet, useIntegrationsGetAvailableTypes, useIntegrationsGetByAddonId, useIntegrationsGetSettings, useIntegrationsList, useIntegrationsSetSettings, useIntegrationsTestConnection, useIntegrationsUpdate, useIntercomEndTalkSession, useIntercomGetStatus, useIntercomHandleAnswer, useIntercomPushTalkAudio, useIntercomStartSession, useIntercomStartTalkSession, useIntercomStopSession, useIsMidWidth, useIsMobile, useLawnMowerControlDock, useLawnMowerControlGetStatus, useLawnMowerControlPause, useLawnMowerControlStartMowing, useLiveBuffer, useLiveEvent, useLlmCancel, useLlmDeleteModel, useLlmDeleteProfile, useLlmGenerate, useLlmGenerateVision, useLlmGetDefaults, useLlmGetRuntimeStatus, useLlmGetUsage, useLlmInstallModel, useLlmListModelCatalog, useLlmListModels, useLlmListNodeModels, useLlmListProfileKinds, useLlmListProfiles, useLlmListRuntimeNodes, useLlmResolveModelRef, useLlmSetDefault, useLlmStartRuntime, useLlmStopRuntime, useLlmTestProfile, useLlmUpsertProfile, useLocalNetworkAddCustomAddress, useLocalNetworkDownloadCa, useLocalNetworkGetAddresses, useLocalNetworkGetAllowedAddresses, useLocalNetworkGetConnectionEndpoints, useLocalNetworkGetHubAddresses, useLocalNetworkGetNotificationEndpoint, useLocalNetworkGetPreferred, useLocalNetworkGetTlsStatus, useLocalNetworkGetViewerEndpoints, useLocalNetworkList, useLocalNetworkRegenerateCertificate, useLocalNetworkRemoveCustomAddress, useLocalNetworkResetAllowlistToBestMatch, useLocalNetworkRevertToGeneratedCertificate, useLocalNetworkSetAddressRole, useLocalNetworkSetAllowedAddresses, useLocalNetworkSetNotificationEndpoint, useLocalNetworkSetViewerEndpoints, useLocalNetworkUploadCertificate, useLockControlGetStatus, useLockControlLock, useLockControlOpen, useLockControlUnlock, useMediaPlayerGetStatus, useMediaPlayerNext, useMediaPlayerPause, useMediaPlayerPlay, useMediaPlayerPlayMedia, useMediaPlayerPrevious, useMediaPlayerSeek, useMediaPlayerSelectSource, useMediaPlayerSetMute, useMediaPlayerSetRepeat, useMediaPlayerSetShuffle, useMediaPlayerSetVolume, useMediaPlayerStop, useMeshNetworkGetStatus, useMeshNetworkJoin, useMeshNetworkLeave, useMeshNetworkListPeers, useMeshNetworkLogout, useMeshNetworkStartLogin, useMeshNetworkTestConnection, useMetricsProviderCollectSnapshot, useMetricsProviderGetAddonStats, useMetricsProviderGetCached, useMetricsProviderGetCpuTemperature, useMetricsProviderGetCurrent, useMetricsProviderGetDiskSpace, useMetricsProviderGetGpuInfo, useMetricsProviderGetLoadSeries, useMetricsProviderGetProcessStats, useMetricsProviderListAddonInstances, useMetricsProviderListNodeProcesses, useMotionDetectionAnalyze, useMotionDetectionApplyDeviceSettingsPatch, useMotionDetectionGetDeviceLiveContribution, useMotionDetectionGetDeviceSettingsContribution, useMotionDetectionRemoveCamera, useMotionDetectionReset, useMotionGetStatus, useMotionIsDetected, useMotionTriggerGetStatus, useMotionTriggerSetMotionTrigger, useMotionZonesGetOptions, useMotionZonesGetStatus, useMotionZonesSetZone, useMqttBrokerAddBroker, useMqttBrokerGetBrokerConfig, useMqttBrokerGetStatus, useMqttBrokerListBrokers, useMqttBrokerRemoveBroker, useMqttBrokerStartEmbeddedBroker, useMqttBrokerStopEmbeddedBroker, useMqttBrokerTestConnection, useNativeObjectDetectionGetOptions, useNativeObjectDetectionGetStatus, useNativeObjectDetectionSetEnabled, useNativeObjectDetectionSetSettings, useNavigation, useNavigationGetFeatures, useNavigationGetStatus, useNavigationGoToPoint, useNavigationListActions, useNavigationMove, useNavigationPlaySound, useNavigationRunAction, useNavigationSetLightLevel, useNavigationSetLightMode, useNavigationSetLightOn, useNavigationStop, useNetworkAccessGetEndpoint, useNetworkAccessGetStatus, useNetworkAccessListEndpoints, useNetworkAccessStart, useNetworkAccessStop, useNetworkLinkGetStatus, useNetworkQualityGetAllStats, useNetworkQualityGetDeviceStats, useNetworkQualityReportClientStats, useNodesClusterAddonStatus, useNodesDeployAddon, useNodesDirectory, useNodesExecuteQuery, useNodesGetCapUsageGraph, useNodesGetNodeAddons, useNodesRenameNode, useNodesRestartAddon, useNodesRestartNode, useNodesRestartProcess, useNodesSetProcessLogLevel, useNodesShutdownNode, useNodesTopology, useNodesUndeployAddon, useNotificationOutputDeleteTarget, useNotificationOutputDiscoverTargets, useNotificationOutputGetTarget, useNotificationOutputListTargetKinds, useNotificationOutputListTargets, useNotificationOutputProbeTargetConfig, useNotificationOutputSend, useNotificationOutputSetTargetEnabled, useNotificationOutputTestTarget, useNotificationOutputUpsertTarget, useNotificationRulesActivateAlarmProfile, useNotificationRulesCancelSnooze, useNotificationRulesCreateAlarmProfile, useNotificationRulesCreateRule, useNotificationRulesCreateSequence, useNotificationRulesCreateSnooze, useNotificationRulesDeleteAlarmProfile, useNotificationRulesDeleteRule, useNotificationRulesDeleteSequence, useNotificationRulesGetAlarmConfig, useNotificationRulesGetAlarmSensors, useNotificationRulesGetClipRetentionAsks, useNotificationRulesGetConditionCatalog, useNotificationRulesGetHistory, useNotificationRulesGetRule, useNotificationRulesGetRuleLastRuns, useNotificationRulesGetSequence, useNotificationRulesGetTemplateCatalog, useNotificationRulesListDeviceMutes, useNotificationRulesListRules, useNotificationRulesListSequences, useNotificationRulesListSnoozes, useNotificationRulesPreviewStateSummary, useNotificationRulesPreviewTemplate, useNotificationRulesRenameAlarmProfile, useNotificationRulesResolveArtifactUrl, useNotificationRulesSetAlarmConfig, useNotificationRulesSetDeviceMuted, useNotificationRulesSetRuleEnabled, useNotificationRulesTestRule, useNotificationRulesUpdateRule, useNotificationRulesUpdateSequence, useNotifierCancel, useNotifierGetStatus, useNotifierSend, useNumericSensorGetStatus, useOptimisticSlice, useOptionalSystem, useOptionalWidgetRegistry, useOsdGetStatus, useOsdManagerClearSlotBinding, useOsdManagerCopyDeviceConfiguration, useOsdManagerDeleteTemplate, useOsdManagerGetConditionSupport, useOsdManagerGetDeviceOsd, useOsdManagerGetSourceCatalog, useOsdManagerListTemplates, useOsdManagerPreviewSlot, useOsdManagerRenderDevice, useOsdManagerSetSlotBinding, useOsdManagerUpsertTemplate, useOsdSetOverlay, usePTZ, usePetFeederCallPet, usePetFeederCancelFeed, usePetFeederFeed, usePetFeederGetStatus, usePetFeederMarkFoodReplenished, usePetFeederPlaySound, usePetFeederResetDesiccant, usePetFeederSetChildLock, usePetFeederSetFeedSound, usePetFeederSetIndicatorLight, usePetFeederSetVolume, usePickerSiteZones, usePipelineAnalyticsApplyDeviceSettingsPatch, usePipelineAnalyticsCancelRelocateMedia, usePipelineAnalyticsCancelStorageMigrationMove, usePipelineAnalyticsClearTrackRelation, usePipelineAnalyticsClearTracks, usePipelineAnalyticsCompleteRetrainTrack, usePipelineAnalyticsCountRelocatableMedia, usePipelineAnalyticsCountUnstampedEventMedia, usePipelineAnalyticsDeleteDeviceEvents, usePipelineAnalyticsDeleteTracks, usePipelineAnalyticsDeselectRetrainFrame, usePipelineAnalyticsGetActiveTracks, usePipelineAnalyticsGetAudioEvents, usePipelineAnalyticsGetDeviceLiveContribution, usePipelineAnalyticsGetDeviceSettingsContribution, usePipelineAnalyticsGetEventDensity, usePipelineAnalyticsGetEventDensityBatch, usePipelineAnalyticsGetEventMedia, usePipelineAnalyticsGetEventMediaFootprintByKind, usePipelineAnalyticsGetEventStoreFootprint, usePipelineAnalyticsGetKeyEvents, usePipelineAnalyticsGetKeyEventsBatch, usePipelineAnalyticsGetMediaReclaimStatus, usePipelineAnalyticsGetMotionEvents, usePipelineAnalyticsGetObjectEmbeddingRebuildStatus, usePipelineAnalyticsGetObjectEvents, usePipelineAnalyticsGetRetrainExportUrl, usePipelineAnalyticsGetRetrainFrameImage, usePipelineAnalyticsGetSensorEvents, usePipelineAnalyticsGetStorageMigrationMoveStatus, usePipelineAnalyticsGetTrack, usePipelineAnalyticsGetTrackMedia, usePipelineAnalyticsGetTrainingExportSummary, usePipelineAnalyticsGetTrainingExportUrl, usePipelineAnalyticsListArchivedDebugNotes, usePipelineAnalyticsListBirthDecisions, usePipelineAnalyticsListEventKinds, usePipelineAnalyticsListEventKindsBatch, usePipelineAnalyticsListEventMedia, usePipelineAnalyticsListOpsLog, usePipelineAnalyticsListRecentTracks, usePipelineAnalyticsListRelocateMediaJobs, usePipelineAnalyticsListRetrainAnnotations, usePipelineAnalyticsListRetrainFrames, usePipelineAnalyticsListRetrainStaging, usePipelineAnalyticsListTrackMedia, usePipelineAnalyticsListTrackRelations, usePipelineAnalyticsListTracks, usePipelineAnalyticsOwnersWithMedia, usePipelineAnalyticsPauseForStorageMigration, usePipelineAnalyticsProposeRetrainAnnotations, usePipelineAnalyticsPruneEvents, usePipelineAnalyticsPruneEventsBefore, usePipelineAnalyticsPruneTracksBefore, usePipelineAnalyticsReadLongTermSeries, usePipelineAnalyticsRebuildObjectEmbeddings, usePipelineAnalyticsReclaimDebugMedia, usePipelineAnalyticsReconcileFromDisk, usePipelineAnalyticsRefreshStorageLocationsForMigration, usePipelineAnalyticsRelocateMedia, usePipelineAnalyticsRestageRetrainTrack, usePipelineAnalyticsResumeForStorageMigration, usePipelineAnalyticsSaveRetrainAnnotations, usePipelineAnalyticsSearchObjectEvents, usePipelineAnalyticsSelectRetrainFrames, usePipelineAnalyticsSetTrackFlags, usePipelineAnalyticsSetTrackPurity, usePipelineAnalyticsSetTrackRelation, usePipelineAnalyticsStartStorageMigrationMove, usePipelineAnalyticsWipeAllAnalytics, usePipelineAnalyticsWipeObjectEmbeddings, usePipelineExecutorCacheFrameInPool, usePipelineExecutorClearDeviceOverrides, usePipelineExecutorClearModelBoundDeviceOverrides, usePipelineExecutorDeleteModel, usePipelineExecutorDeleteTemplate, usePipelineExecutorDownloadModel, usePipelineExecutorGetAddonModels, usePipelineExecutorGetAudioCapabilities, usePipelineExecutorGetAvailableEngines, usePipelineExecutorGetCapabilities, usePipelineExecutorGetDefaultSteps, usePipelineExecutorGetDetectionConfigSchema, usePipelineExecutorGetEffectiveTuning, usePipelineExecutorGetEngineProvisioning, usePipelineExecutorGetGlobalPipelineConfig, usePipelineExecutorGetGlobalSteps, usePipelineExecutorGetInferenceDeviceHealth, usePipelineExecutorGetOrchestratorConfigSchema, usePipelineExecutorGetReferenceAudio, usePipelineExecutorGetReferenceAudioFiles, usePipelineExecutorGetReferenceImage, usePipelineExecutorGetSchema, usePipelineExecutorGetSelectedEngine, usePipelineExecutorGetVideoPipelineSteps, usePipelineExecutorInferCached, usePipelineExecutorKillEngine, usePipelineExecutorListLoadedEngines, usePipelineExecutorListReferenceImages, usePipelineExecutorListTemplates, usePipelineExecutorRearmInferenceDevice, usePipelineExecutorRunAudioTest, usePipelineExecutorRunPipeline, usePipelineExecutorRunPipelineBatch, usePipelineExecutorSaveTemplate, usePipelineExecutorSetVideoPipelineSteps, usePipelineExecutorSpinEngine, usePipelineExecutorUncacheFrame, usePipelineExecutorUpdateTemplate, usePipelineExecutorValidatePipeline, usePipelineOrchestratorApplyAutoTune, usePipelineOrchestratorApplyDeviceSettingsPatch, usePipelineOrchestratorAssignAudio, usePipelineOrchestratorAssignPipeline, usePipelineOrchestratorDeleteTemplate, usePipelineOrchestratorGetAgentLoad, usePipelineOrchestratorGetAgentSettings, usePipelineOrchestratorGetAudioAssignment, usePipelineOrchestratorGetAudioAssignments, usePipelineOrchestratorGetAudioNodeLoad, usePipelineOrchestratorGetCameraMetrics, usePipelineOrchestratorGetCameraSettings, usePipelineOrchestratorGetCameraStatus, usePipelineOrchestratorGetCameraStatuses, usePipelineOrchestratorGetCameraStepOverrides, usePipelineOrchestratorGetCameraSwitches, usePipelineOrchestratorGetCapabilityBindings, usePipelineOrchestratorGetClusterModelChoice, usePipelineOrchestratorGetDeviceLiveContribution, usePipelineOrchestratorGetDeviceSettingsContribution, usePipelineOrchestratorGetEffectiveShares, usePipelineOrchestratorGetGlobalMetrics, usePipelineOrchestratorGetIngestOwner, usePipelineOrchestratorGetNodeInferenceDevices, usePipelineOrchestratorGetPipelineAssignment, usePipelineOrchestratorGetPipelineAssignments, usePipelineOrchestratorGetPipelineDevicePin, usePipelineOrchestratorGetPlacementStats, usePipelineOrchestratorGetReconcileFromDiskStatus, usePipelineOrchestratorListAgentSettings, usePipelineOrchestratorListTemplates, usePipelineOrchestratorPauseForStorageMigration, usePipelineOrchestratorPlanAutoTune, usePipelineOrchestratorRebalance, usePipelineOrchestratorReconcileFromDisk, usePipelineOrchestratorRemoveAgentSettings, usePipelineOrchestratorRequestOccupancyLook, usePipelineOrchestratorResetNodePipelineDefaults, usePipelineOrchestratorResolvePipeline, usePipelineOrchestratorResumeForStorageMigration, usePipelineOrchestratorSaveTemplate, usePipelineOrchestratorSetAgentCapabilities, usePipelineOrchestratorSetAgentDecodeLimits, usePipelineOrchestratorSetAgentInferenceDevices, usePipelineOrchestratorSetAgentMaxCameras, usePipelineOrchestratorSetAgentReachableHost, usePipelineOrchestratorSetCameraPipelineForAgent, usePipelineOrchestratorSetCameraSensors, usePipelineOrchestratorSetCameraStepOverride, usePipelineOrchestratorSetCameraStepToggle, usePipelineOrchestratorSetCameraSwitch, usePipelineOrchestratorSetCapabilityBinding, usePipelineOrchestratorSetLoadBalancingShares, usePipelineOrchestratorSetPipelineDevicePin, usePipelineOrchestratorSetShareSource, usePipelineOrchestratorSuggestShares, usePipelineOrchestratorUnassignAudio, usePipelineOrchestratorUnassignPipeline, usePipelineOrchestratorUpdateTemplate, usePipelineRunnerAttachCamera, usePipelineRunnerDetachCamera, usePipelineRunnerGetAllCameraMetrics, usePipelineRunnerGetCameraMetrics, usePipelineRunnerGetLocalCameras, usePipelineRunnerGetLocalLoad, usePipelineRunnerGetLocalMetrics, usePipelineRunnerGetNativeCrop, usePipelineRunnerGetParkedTrackFrame, usePipelineRunnerParkTrackFrame, usePipelineRunnerReleaseParkedTrackFrames, usePipelineRunnerReportMotion, usePipelineRunnerRequestOccupancyBurst, usePipelineRunnerRunDetailSubtree, usePipelineRunnerRunStatelessStep, usePlateGalleryAssignPlate, usePlateGalleryAssignPlates, usePlateGalleryCorrectPlateText, usePlateGalleryCreateVehicle, usePlateGalleryDeletePlate, usePlateGalleryDeleteVehicle, usePlateGalleryGetPlateByTrack, usePlateGalleryGetPlateMedia, usePlateGalleryListPlates, usePlateGalleryListVehicleSamples, usePlateGalleryListVehicles, usePlateGalleryRemoveVehicleSample, usePlateGalleryRenameVehicle, usePlateGallerySearchPlates, usePlateGallerySuggestPlateClusters, usePlateGalleryUnassignPlate, usePlateGalleryUnassignPlates, usePlayerOverlayLayer, usePlayerOverlayLayers, usePlayerToolbarButton, usePlayerToolbarButtons, usePowerMeterGetStatus, usePresenceGetStatus, usePressureSensorGetStatus, usePrivacyMaskGetOptions, usePrivacyMaskGetStatus, usePrivacyMaskSetAudioEnabled, usePrivacyMaskSetMask, usePrompt, usePtzAutotrackGetSettings, usePtzAutotrackGetStatus, usePtzAutotrackSetEnabled, usePtzAutotrackSetSettings, usePtzCaptureHomeHere, usePtzContinuousMove, usePtzDeletePreset, usePtzGetHomePreset, usePtzGetHomeReturn, usePtzGetOptions, usePtzGetPosition, usePtzGetPresets, usePtzGetStatus, usePtzGoHome, usePtzGoToPreset, usePtzMove, usePtzSavePreset, usePtzSetAutofocus, usePtzSetHomePreset, usePtzSetHomeReturn, usePtzStop, useRebootReboot, useRecordedPlayback, useRecordingArchiveApplyDeviceSettingsPatch, useRecordingArchiveCancelRelocateJob, useRecordingArchiveCancelStorageMigrationMove, useRecordingArchiveDeleteFootprint, useRecordingArchiveGetAvailabilityBatch, useRecordingArchiveGetDaysWithRecordingsBatch, useRecordingArchiveGetDeviceConfig, useRecordingArchiveGetDeviceLiveContribution, useRecordingArchiveGetDeviceSettingsContribution, useRecordingArchiveGetPlacement, useRecordingArchiveGetRelocateResidue, useRecordingArchiveGetStatus, useRecordingArchiveGetStorageMigrationMoveStatus, useRecordingArchiveGetStorageUsage, useRecordingArchiveListOpsLog, useRecordingArchiveListRelocateJobs, useRecordingArchiveLocateSegment, useRecordingArchivePauseForStorageMigration, useRecordingArchivePlanStorageRebalance, useRecordingArchivePruneFootage, useRecordingArchiveReadGopBytes, useRecordingArchiveReadSegmentBytes, useRecordingArchiveReadWindowBytes, useRecordingArchiveReconcileLedgerAgainstDisk, useRecordingArchiveRefreshStorageLocationsForMigration, useRecordingArchiveRelocateFootage, useRecordingArchiveRenderClip, useRecordingArchiveRenderGif, useRecordingArchiveRescanStorage, useRecordingArchiveResumeForStorageMigration, useRecordingArchiveSetDeviceConfig, useRecordingArchiveSetDevicePlacement, useRecordingArchiveStartStorageMigrationMove, useRecordingArchiveStartStorageRebalance, useRecordingExportCancelExport, useRecordingExportCreateExport, useRecordingExportDeleteExport, useRecordingExportGetDownloadUrl, useRecordingExportGetExport, useRecordingExportListExports, useRecordingExportReadExportBytes, useRecordingGetAvailability, useRecordingGetDaysWithRecordings, useRecordingGetPlayback, useRecordingGetPlaybackOptions, useRecordingListSources, useRecordingOnboardGetOptions, useRecordingOnboardGetStatus, useRecordingOnboardSetSettings, useRecordingSignalGetStatus, useRemoteComponent, useRoleDisplayDefaults, useSceneMonitorCaptureReference, useSceneMonitorCreateScene, useSceneMonitorDeleteReference, useSceneMonitorDeleteScene, useSceneMonitorGetStatus, useSceneMonitorListScenes, useSceneMonitorListScenesBatch, useSceneMonitorRecheckNow, useSceneMonitorResetScene, useSceneMonitorUpdateScene, useScriptRunnerGetStatus, useScriptRunnerRun, useScriptRunnerStop, useScrubController, useServerManagementApplyServerUpdate, useServerManagementCheckServerUpdate, useServerManagementGetServerPackageStatus, useServerManagementRestartServer, useServerManagementRollbackServerUpdate, useSettingsStoreAggregate, useSettingsStoreCount, useSettingsStoreDeclareCollection, useSettingsStoreDelete, useSettingsStoreDeleteWhere, useSettingsStoreGet, useSettingsStoreHistogram, useSettingsStoreInsert, useSettingsStoreInsertMany, useSettingsStoreIsEmpty, useSettingsStoreQuery, useSettingsStoreSet, useSettingsStoreUpdate, useSettingsStoreUpdateWhere, useSettingsStoreUpsert, useSmokeGetStatus, useSnapshotApplyDeviceSettingsPatch, useSnapshotGetDebugState, useSnapshotGetDeviceLiveContribution, useSnapshotGetDeviceSettingsContribution, useSnapshotGetSnapshot, useSnapshotGetSnapshotLinks, useSnapshotGetSnapshotOverview, useSnapshotGetStatus, useSnapshotInvalidateCache, useStationaryOverlay, useStorageAbortUpload, useStorageBeginDownload, useStorageBeginUpload, useStorageDelete, useStorageDeleteLocation, useStorageEndDownload, useStorageExists, useStorageFinalizeUpload, useStorageGetAvailableSpace, useStorageList, useStorageListDrainProgress, useStorageListLocationDeclarations, useStorageListLocations, useStorageListProviders, useStorageMigrationCancel, useStorageMigrationCleanupCancel, useStorageMigrationCleanupStart, useStorageMigrationCleanupStatus, useStorageMigrationDrain, useStorageMigrationHistory, useStorageMigrationMovers, useStorageMigrationPlan, useStorageMigrationResidue, useStorageMigrationStart, useStorageMigrationStatus, useStorageRead, useStorageReadChunk, useStorageResolve, useStorageTestConfig, useStorageTestLocation, useStorageUpsertLocation, useStorageWrite, useStorageWriteChunk, useStoredGroupBy, useStreamBrokerAcquireEgressTranscode, useStreamBrokerApplyDeviceSettingsPatch, useStreamBrokerAssignProfile, useStreamBrokerFetchEventMedia, useStreamBrokerForgetDeviceHardware, useStreamBrokerGetAllRtspEntries, useStreamBrokerGetBrokerStats, useStreamBrokerGetDeviceAudioMute, useStreamBrokerGetDeviceLiveContribution, useStreamBrokerGetDeviceSettingsContribution, useStreamBrokerGetPreBufferInfo, useStreamBrokerGetRtspEntry, useStreamBrokerGetRtspPort, useStreamBrokerGetStreamUrl, useStreamBrokerGetStreamWithCodec, useStreamBrokerIsRtspEnabled, useStreamBrokerKillClient, useStreamBrokerListAllCameraStreams, useStreamBrokerListAllProfileSlots, useStreamBrokerListClients, useStreamBrokerProbeStream, useStreamBrokerProduceEventMedia, useStreamBrokerPublishCameraStream, useStreamBrokerPullAudioChunks, useStreamBrokerPullFrameHandles, useStreamBrokerRegenerateRtspToken, useStreamBrokerReleaseEgressTranscode, useStreamBrokerReleaseStreamWithCodec, useStreamBrokerRenderPreBufferClip, useStreamBrokerReportStreamSignal, useStreamBrokerRestartProfile, useStreamBrokerRetractCameraStream, useStreamBrokerSetDeviceAudioMute, useStreamBrokerSetPreBufferDuration, useStreamBrokerSetRtspEnabled, useStreamBrokerSubscribeAudioChunks, useStreamBrokerSubscribeFrames, useStreamBrokerUnassignProfile, useStreamBrokerUnsubscribeAudioChunks, useStreamBrokerUnsubscribeFrames, useStreamCatalogGetCatalog, useStreamParamsGetConfigSchema, useStreamParamsGetOptions, useStreamParamsGetStatus, useStreamParamsSetProfile, useStreamSignalsListSignals, useSwitchGetStatus, useSwitchSetState, useSystem, useSystemDetectSiteLocation, useSystemFeatureFlags, useSystemForceRetentionCleanup, useSystemGetFailureContributions, useSystemGetLoadContributions, useSystemGetLoggingSettings, useSystemGetRequestCensus, useSystemGetRetentionConfig, useSystemGetSiteLocation, useSystemHealth, useSystemInfo, useSystemMutation, useSystemNetworkAddresses, useSystemQuery, useSystemSetLoggingSettings, useSystemSetRetentionConfig, useSystemSetSiteLocation, useTamperGetStatus, useTemperatureSensorGetStatus, useTerminalSessionAdoptLegacyMonitor, useTerminalSessionClose, useTerminalSessionCreateInstance, useTerminalSessionDeleteInstance, useTerminalSessionListInstances, useTerminalSessionListLegacyCameras, useTerminalSessionListProfiles, useTerminalSessionListSessions, useTerminalSessionOpenSession, useTerminalSessionPullOutput, useTerminalSessionResize, useTerminalSessionSetInstanceEnabled, useTerminalSessionUpdateInstance, useTerminalSessionWriteInput, useThemeMode, useToastOnToast, useTurnProviderGetTurnServers, useUpdateGetStatus, useUpdateInstallUpdate, useUrlQueryParam, useUserManagementConfirmTotp, useUserManagementCreateApiKey, useUserManagementCreateScopedToken, useUserManagementCreateUser, useUserManagementDeleteUser, useUserManagementDisableTotp, useUserManagementGetTotpStatus, useUserManagementListApiKeys, useUserManagementListOauthSessions, useUserManagementListScopedTokens, useUserManagementListUsers, useUserManagementOauthExchangeCode, useUserManagementOauthIssueCode, useUserManagementOauthRefresh, useUserManagementOauthVerifyAccessToken, useUserManagementResetPassword, useUserManagementRevokeApiKey, useUserManagementRevokeOauthSession, useUserManagementRevokeScopedToken, useUserManagementSetUserScopes, useUserManagementSetupTotp, useUserManagementUpdateUser, useUserManagementValidateApiKey, useUserManagementValidateCredentials, useUserManagementValidateScopedToken, useUserManagementVerifyTotp, useVacuumControlGetStatus, useVacuumControlLocate, useVacuumControlPause, useVacuumControlReturnToBase, useVacuumControlSetFanSpeed, useVacuumControlStart, useVacuumControlStop, useValveClose, useValveGetStatus, useValveOpen, useValveSetPosition, useValveStop, useVibrationGetStatus, useVideoclipsDialClipStream, useVideoclipsGetClipPlayback, useVideoclipsGetPlaybackOptions, useVideoclipsListClips, useVideoclipsListSources, useVideoclipsOfferClipBytes, useVideoclipsReadClipBytes, useVodPlayback, useWaterHeaterGetStatus, useWaterHeaterSetAway, useWaterHeaterSetOperationMode, useWaterHeaterSetTargetTemp, useWeatherGetStatus, useWebrtcSessionAddIceCandidate, useWebrtcSessionApplyDownlinkProbe, useWebrtcSessionCloseSession, useWebrtcSessionCreateSession, useWebrtcSessionGetIceCandidates, useWebrtcSessionGetSessionDebug, useWebrtcSessionGetSessionState, useWebrtcSessionHandleAnswer, useWebrtcSessionHandleOffer, useWebrtcSessionHasAdaptiveBitrate, useWebrtcSessionListStreams, useWidget, useWidgetMetadata, useWidgetRegistry, useZoneAnalyticsGetCameraHistory, useZoneAnalyticsGetCurrentSnapshot, useZoneAnalyticsGetCurrentSnapshotBatch, useZoneAnalyticsGetUnzonedHistory, useZoneAnalyticsGetZoneHistory, useZoneEditing, useZoneRulesListRules, useZoneRulesSetRules, useZonesAddZone, useZonesListZones, useZonesRemoveZone, useZonesUpdateZone, vacuumStateMeta, validateScopes, valveStateMeta, variantLabel, waterHeaterPhase, waterHeaterTint, weatherConditionMeta, weatherTint, withResolvedParentDetail, writeStoredGroupBy };