@camstack/ui-library 1.2.248 → 1.2.250

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
@@ -1,13 +1,13 @@
1
1
  import { ThemeProvider, createTheme, darkColors, defaultTheme, lightColors, themeToCss, useThemeMode } from "./theme/index.js";
2
2
  import * as __mfHostReact from "react";
3
3
  import React, { Fragment, Suspense, createContext, createElement, forwardRef, lazy, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore } from "react";
4
+ import { ALL_CAPABILITY_DEFINITIONS, CAM_PROFILE_ORDER, CLIP_STILL_DISPOSITION_HEADER, CLIP_STILL_REASON_CODE_HEADER, CLIP_STILL_REASON_HEADER, CONTAINER_CHILD_PRIORITY, DEFAULT_EVENTS_BAND_BUFFER_SEC, DEFAULT_EVENT_COLOR, DEVICE_TYPE_CONTROL_KIND, DISPLAY_ICON_GROUPS, DeviceExportStatusSchema, DeviceFeature, DeviceRole, DeviceType, EVENT_TAXONOMY, EXTENSION_SLOTS, EXTENSION_SLOT_IDS, EventCategory, ExposedDeviceSchema, FIRST_LEVEL_MACRO_CLASSES, KNOWN_CAP_NAMES, MAX_SENSOR_TRIGGER_DEVICES, MODEL_PROVIDER_IDS, NAVIGATION_LIGHT_LEVEL_MAX, NAVIGATION_LIGHT_LEVEL_MIN, PET_FEEDER_MANUAL_FEED_MAX, PET_FEEDER_MANUAL_FEED_MIN, RECORDING_AUDIO_TRIGGER_OFFERED_DBFS, RECORDING_SOURCE_CAMSTACK_ADDON, RecordingConfigSchema, RecordingStatusSchema, SECRET_CLEAR, SOURCE_CAP_ACTIVE_FIELD, STATIONARY_OVERLAY_STYLE, TIMEZONES, USE_ROOT_MODEL_SETTING_KEY, buildModelVariantGroups, canConvertUnit, clearModelBoundOverrides, clearModelBoundSetting, clusterStepSettingFieldsFor, collectModelBoundKeys, createDeviceProxy, createLazyTrpcSource, createSliceHandle, deriveRecordingMode, describeModelVariant, effectiveStepSettings, findTimezone, formatParkedFor, hasModelBoundOverrides, isKeychainStoredField, isRootSuppliableStepId, isSecretClear, isSecretFieldView, modelBindingKey, normalizeUnit, publicEventCategories, resolveContainerPrimaryChild, resolveDeviceControlKind, resolveDisplayIconKey, resolveModelBoundSetting, resolveRecordingProfiles, resolveRootSupply, resolveVariantModelId, resolveViewableDeviceIds, searchDisplayIcons, selectAssignedProfileSlots, stepSettingSource, tryConvertUnit, unitsForDimension, withFieldFrom, withoutField, writeModelBoundSetting } from "@camstack/types";
4
5
  import * as __mfHostTrpcReactQuery from "@trpc/react-query";
5
6
  import { createTRPCReact } from "@trpc/react-query";
6
7
  import * as __mfHostReactJsxRuntime from "react/jsx-runtime";
7
8
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
9
  import * as __mfHostReactDOM from "react-dom";
9
10
  import { createPortal } from "react-dom";
10
- import { ALL_CAPABILITY_DEFINITIONS, CAM_PROFILE_ORDER, CLIP_STILL_DISPOSITION_HEADER, CLIP_STILL_REASON_CODE_HEADER, CLIP_STILL_REASON_HEADER, CONTAINER_CHILD_PRIORITY, DEFAULT_EVENTS_BAND_BUFFER_SEC, DEFAULT_EVENT_COLOR, DEVICE_TYPE_CONTROL_KIND, DISPLAY_ICON_GROUPS, DeviceExportStatusSchema, DeviceFeature, DeviceRole, DeviceType, EVENT_TAXONOMY, EXTENSION_SLOTS, EXTENSION_SLOT_IDS, EventCategory, ExposedDeviceSchema, FIRST_LEVEL_MACRO_CLASSES, KNOWN_CAP_NAMES, MAX_SENSOR_TRIGGER_DEVICES, MODEL_PROVIDER_IDS, NAVIGATION_LIGHT_LEVEL_MAX, NAVIGATION_LIGHT_LEVEL_MIN, PET_FEEDER_MANUAL_FEED_MAX, PET_FEEDER_MANUAL_FEED_MIN, RECORDING_AUDIO_TRIGGER_OFFERED_DBFS, RECORDING_SOURCE_CAMSTACK_ADDON, RecordingConfigSchema, RecordingStatusSchema, SOURCE_CAP_ACTIVE_FIELD, STATIONARY_OVERLAY_STYLE, TIMEZONES, USE_ROOT_MODEL_SETTING_KEY, buildModelVariantGroups, canConvertUnit, clearModelBoundOverrides, clearModelBoundSetting, clusterStepSettingFieldsFor, collectModelBoundKeys, createDeviceProxy, createLazyTrpcSource, createSliceHandle, deriveRecordingMode, describeModelVariant, effectiveStepSettings, findTimezone, formatParkedFor, hasModelBoundOverrides, isRootSuppliableStepId, modelBindingKey, normalizeUnit, publicEventCategories, resolveContainerPrimaryChild, resolveDeviceControlKind, resolveModelBoundSetting, resolveRecordingProfiles, resolveRootSupply, resolveVariantModelId, resolveViewableDeviceIds, searchDisplayIcons, selectAssignedProfileSlots, stepSettingSource, tryConvertUnit, unitsForDimension, withFieldFrom, withoutField, writeModelBoundSetting } from "@camstack/types";
11
11
  import * as __mfHostTanstackQuery from "@tanstack/react-query";
12
12
  import { QueryClient, QueryClientProvider, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
13
13
  import * as __mfHostReactDOMClient from "react-dom/client";
@@ -2430,6 +2430,26 @@ var Gauge = createLucideIcon("gauge", [["path", {
2430
2430
  * This source code is licensed under the ISC license.
2431
2431
  * See the LICENSE file in the root directory of this source tree.
2432
2432
  */
2433
+ var GraduationCap = createLucideIcon("graduation-cap", [
2434
+ ["path", {
2435
+ d: "M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0z",
2436
+ key: "j76jl0"
2437
+ }],
2438
+ ["path", {
2439
+ d: "M22 10v6",
2440
+ key: "1lu8f3"
2441
+ }],
2442
+ ["path", {
2443
+ d: "M6 12.5V16a6 3 0 0 0 12 0v-3.5",
2444
+ key: "1r8lef"
2445
+ }]
2446
+ ]);
2447
+ /**
2448
+ * @license lucide-react v0.576.0 - ISC
2449
+ *
2450
+ * This source code is licensed under the ISC license.
2451
+ * See the LICENSE file in the root directory of this source tree.
2452
+ */
2433
2453
  var Grid2x2 = createLucideIcon("grid-2x2", [
2434
2454
  ["path", {
2435
2455
  d: "M12 3v18",
@@ -6442,140 +6462,1063 @@ function useClusterNodes() {
6442
6462
  };
6443
6463
  }
6444
6464
  //#endregion
6445
- //#region src/generated/system-hooks.ts
6465
+ //#region src/composites/device-meta.ts
6466
+ var DEVICE_TYPE_META = {
6467
+ [DeviceType.Camera]: {
6468
+ icon: Camera,
6469
+ label: "Camera"
6470
+ },
6471
+ [DeviceType.Hub]: {
6472
+ icon: Network,
6473
+ label: "Hub"
6474
+ },
6475
+ [DeviceType.Light]: {
6476
+ icon: Lightbulb,
6477
+ label: "Light"
6478
+ },
6479
+ [DeviceType.Siren]: {
6480
+ icon: Siren,
6481
+ label: "Siren"
6482
+ },
6483
+ [DeviceType.Switch]: {
6484
+ icon: ToggleLeft,
6485
+ label: "Switch"
6486
+ },
6487
+ [DeviceType.Sensor]: {
6488
+ icon: Activity,
6489
+ label: "Sensor"
6490
+ },
6491
+ [DeviceType.Thermostat]: {
6492
+ icon: Thermometer,
6493
+ label: "Thermostat"
6494
+ },
6495
+ [DeviceType.Climate]: {
6496
+ icon: AirVent,
6497
+ label: "Air conditioner"
6498
+ },
6499
+ [DeviceType.Button]: {
6500
+ icon: CircleDot,
6501
+ label: "Button"
6502
+ },
6503
+ [DeviceType.EventEmitter]: {
6504
+ icon: Zap,
6505
+ label: "Event Emitter"
6506
+ },
6507
+ [DeviceType.Update]: {
6508
+ icon: RefreshCw,
6509
+ label: "Update"
6510
+ },
6511
+ [DeviceType.Generic]: {
6512
+ icon: Box,
6513
+ label: "Generic"
6514
+ },
6515
+ [DeviceType.Notifier]: {
6516
+ icon: Bell,
6517
+ label: "Notifier"
6518
+ },
6519
+ [DeviceType.Script]: {
6520
+ icon: ScrollText,
6521
+ label: "Script"
6522
+ },
6523
+ [DeviceType.Automation]: {
6524
+ icon: Workflow,
6525
+ label: "Automation"
6526
+ },
6527
+ [DeviceType.Lock]: {
6528
+ icon: Lock,
6529
+ label: "Lock"
6530
+ },
6531
+ [DeviceType.Cover]: {
6532
+ icon: Blinds,
6533
+ label: "Cover"
6534
+ },
6535
+ [DeviceType.Valve]: {
6536
+ icon: Pipette,
6537
+ label: "Valve"
6538
+ },
6539
+ [DeviceType.Humidifier]: {
6540
+ icon: Waves,
6541
+ label: "Humidifier"
6542
+ },
6543
+ [DeviceType.WaterHeater]: {
6544
+ icon: Bath,
6545
+ label: "Water heater"
6546
+ },
6547
+ [DeviceType.Fan]: {
6548
+ icon: Fan,
6549
+ label: "Fan"
6550
+ },
6551
+ [DeviceType.MediaPlayer]: {
6552
+ icon: MonitorPlay,
6553
+ label: "Media player"
6554
+ },
6555
+ [DeviceType.AlarmPanel]: {
6556
+ icon: ShieldAlert,
6557
+ label: "Alarm panel"
6558
+ },
6559
+ [DeviceType.Control]: {
6560
+ icon: SlidersHorizontal,
6561
+ label: "Control"
6562
+ },
6563
+ [DeviceType.Presence]: {
6564
+ icon: MapPin,
6565
+ label: "Presence"
6566
+ },
6567
+ [DeviceType.Weather]: {
6568
+ icon: CloudSun,
6569
+ label: "Weather"
6570
+ },
6571
+ [DeviceType.Vacuum]: {
6572
+ icon: Bot,
6573
+ label: "Vacuum"
6574
+ },
6575
+ [DeviceType.LawnMower]: {
6576
+ icon: Tractor,
6577
+ label: "Lawn mower"
6578
+ },
6579
+ [DeviceType.Container]: {
6580
+ icon: Box,
6581
+ label: "Container"
6582
+ },
6583
+ [DeviceType.Image]: {
6584
+ icon: Image,
6585
+ label: "Image"
6586
+ },
6587
+ [DeviceType.PetFeeder]: {
6588
+ icon: Bone,
6589
+ label: "Pet feeder"
6590
+ }
6591
+ };
6592
+ var DEVICE_ROLE_META = {
6593
+ [DeviceRole.Siren]: {
6594
+ icon: Siren,
6595
+ label: "Siren"
6596
+ },
6597
+ [DeviceRole.Floodlight]: {
6598
+ icon: Lightbulb,
6599
+ label: "Floodlight"
6600
+ },
6601
+ [DeviceRole.Spotlight]: {
6602
+ icon: Flashlight,
6603
+ label: "Spotlight"
6604
+ },
6605
+ [DeviceRole.PirSensor]: {
6606
+ icon: Radar,
6607
+ label: "PIR sensor"
6608
+ },
6609
+ [DeviceRole.Chime]: {
6610
+ icon: Bell,
6611
+ label: "Chime"
6612
+ },
6613
+ [DeviceRole.Autotrack]: {
6614
+ icon: Crosshair,
6615
+ label: "Auto-track"
6616
+ },
6617
+ [DeviceRole.Nightvision]: {
6618
+ icon: Moon,
6619
+ label: "Night vision"
6620
+ },
6621
+ [DeviceRole.PrivacyMask]: {
6622
+ icon: EyeOff,
6623
+ label: "Privacy mask"
6624
+ },
6625
+ [DeviceRole.Doorbell]: {
6626
+ icon: DoorClosed,
6627
+ label: "Doorbell"
6628
+ },
6629
+ [DeviceRole.BinaryHelper]: {
6630
+ icon: ToggleLeft,
6631
+ label: "Binary helper"
6632
+ },
6633
+ [DeviceRole.MotionSensor]: {
6634
+ icon: Footprints,
6635
+ label: "Motion sensor"
6636
+ },
6637
+ [DeviceRole.ContactSensor]: {
6638
+ icon: DoorOpen,
6639
+ label: "Contact sensor"
6640
+ },
6641
+ [DeviceRole.LeakSensor]: {
6642
+ icon: Droplet,
6643
+ label: "Leak sensor"
6644
+ },
6645
+ [DeviceRole.SmokeSensor]: {
6646
+ icon: AlarmSmoke,
6647
+ label: "Smoke sensor"
6648
+ },
6649
+ [DeviceRole.COSensor]: {
6650
+ icon: Wind,
6651
+ label: "CO sensor"
6652
+ },
6653
+ [DeviceRole.GasSensor]: {
6654
+ icon: Flame,
6655
+ label: "Gas sensor"
6656
+ },
6657
+ [DeviceRole.TamperSensor]: {
6658
+ icon: ShieldAlert,
6659
+ label: "Tamper sensor"
6660
+ },
6661
+ [DeviceRole.VibrationSensor]: {
6662
+ icon: Vibrate,
6663
+ label: "Vibration sensor"
6664
+ },
6665
+ [DeviceRole.ConnectivitySensor]: {
6666
+ icon: Wifi,
6667
+ label: "Connectivity"
6668
+ },
6669
+ [DeviceRole.SoundSensor]: {
6670
+ icon: Volume2,
6671
+ label: "Sound sensor"
6672
+ },
6673
+ [DeviceRole.BinarySensor]: {
6674
+ icon: ToggleLeft,
6675
+ label: "Binary sensor"
6676
+ },
6677
+ [DeviceRole.TemperatureSensor]: {
6678
+ icon: Thermometer,
6679
+ label: "Temperature"
6680
+ },
6681
+ [DeviceRole.HumiditySensor]: {
6682
+ icon: Droplets,
6683
+ label: "Humidity"
6684
+ },
6685
+ [DeviceRole.AmbientLightSensor]: {
6686
+ icon: Sun,
6687
+ label: "Ambient light"
6688
+ },
6689
+ [DeviceRole.PressureSensor]: {
6690
+ icon: Gauge,
6691
+ label: "Pressure"
6692
+ },
6693
+ [DeviceRole.WindSensor]: {
6694
+ icon: Wind,
6695
+ label: "Wind"
6696
+ },
6697
+ [DeviceRole.RainSensor]: {
6698
+ icon: CloudRain,
6699
+ label: "Rain"
6700
+ },
6701
+ [DeviceRole.UvSensor]: {
6702
+ icon: SunMedium,
6703
+ label: "UV index"
6704
+ },
6705
+ [DeviceRole.SolarRadiationSensor]: {
6706
+ icon: Sunrise,
6707
+ label: "Solar radiation"
6708
+ },
6709
+ [DeviceRole.SoilMoistureSensor]: {
6710
+ icon: Sprout,
6711
+ label: "Soil moisture"
6712
+ },
6713
+ [DeviceRole.PowerSensor]: {
6714
+ icon: Zap,
6715
+ label: "Power"
6716
+ },
6717
+ [DeviceRole.EnergySensor]: {
6718
+ icon: Zap,
6719
+ label: "Energy"
6720
+ },
6721
+ [DeviceRole.VoltageSensor]: {
6722
+ icon: Zap,
6723
+ label: "Voltage"
6724
+ },
6725
+ [DeviceRole.CurrentSensor]: {
6726
+ icon: Zap,
6727
+ label: "Current"
6728
+ },
6729
+ [DeviceRole.AirQualitySensor]: {
6730
+ icon: Wind,
6731
+ label: "Air quality"
6732
+ },
6733
+ [DeviceRole.BatterySensor]: {
6734
+ icon: Gauge,
6735
+ label: "Battery"
6736
+ },
6737
+ [DeviceRole.NumericSensor]: {
6738
+ icon: Hash,
6739
+ label: "Numeric sensor"
6740
+ },
6741
+ [DeviceRole.EnumSensor]: {
6742
+ icon: List,
6743
+ label: "Enum sensor"
6744
+ },
6745
+ [DeviceRole.DateTimeSensor]: {
6746
+ icon: Clock,
6747
+ label: "Date / time"
6748
+ },
6749
+ [DeviceRole.GenericSensor]: {
6750
+ icon: Gauge,
6751
+ label: "Sensor"
6752
+ },
6753
+ [DeviceRole.NumericControl]: {
6754
+ icon: SlidersHorizontal,
6755
+ label: "Numeric control"
6756
+ },
6757
+ [DeviceRole.SelectControl]: {
6758
+ icon: ListChecks,
6759
+ label: "Select control"
6760
+ },
6761
+ [DeviceRole.TextControl]: {
6762
+ icon: Type,
6763
+ label: "Text control"
6764
+ },
6765
+ [DeviceRole.DateTimeControl]: {
6766
+ icon: Clock,
6767
+ label: "Date / time control"
6768
+ },
6769
+ [DeviceRole.MobilePushNotifier]: {
6770
+ icon: Smartphone,
6771
+ label: "Mobile push"
6772
+ },
6773
+ [DeviceRole.MessagingNotifier]: {
6774
+ icon: MessageSquare,
6775
+ label: "Messaging"
6776
+ },
6777
+ [DeviceRole.EmailNotifier]: {
6778
+ icon: Mail,
6779
+ label: "Email"
6780
+ },
6781
+ [DeviceRole.GenericNotifier]: {
6782
+ icon: Bell,
6783
+ label: "Notifier"
6784
+ }
6785
+ };
6786
+ var DEVICE_TYPE_VALUES$1 = new Set(Object.values(DeviceType));
6787
+ var DEVICE_ROLE_VALUES = new Set(Object.values(DeviceRole));
6788
+ function isDeviceType(value) {
6789
+ return DEVICE_TYPE_VALUES$1.has(value);
6790
+ }
6791
+ function isDeviceRole(value) {
6792
+ return DEVICE_ROLE_VALUES.has(value);
6793
+ }
6794
+ /** Meta for a known `DeviceType`. */
6795
+ function deviceTypeMeta(type) {
6796
+ return DEVICE_TYPE_META[type];
6797
+ }
6798
+ /** Meta for a known `DeviceRole`. */
6799
+ function deviceRoleMeta(role) {
6800
+ return DEVICE_ROLE_META[role];
6801
+ }
6802
+ var FALLBACK_DEVICE_TYPE_META = DEVICE_TYPE_META[DeviceType.Generic];
6803
+ var FALLBACK_DEVICE_ROLE_META = DEVICE_ROLE_META[DeviceRole.GenericSensor];
6446
6804
  /**
6447
- * Per-method React-Query hooks for every `scope: 'system'` capability.
6805
+ * Runtime-string-safe lookup: device data crosses process / network boundaries
6806
+ * as plain strings, so an unrecognised value falls back to the `Generic` meta
6807
+ * instead of throwing.
6448
6808
  *
6449
- * Each hook is a thin pass-through to `trpc.<cap>.<method>.useQuery` /
6450
- * `.useMutation` / `.useSubscription` so the typed inference + RQ
6451
- * cache/dedup keep working — the generator only contributes a consistent
6452
- * name and stable export surface so admin-ui pages can write
6453
- * `useAddonsList()` instead of `trpc.addons.list.useQuery()`.
6809
+ * This also guards mixed-version module-federation loads where the host
6810
+ * `@camstack/types` enum has a new member before the loaded ui-library bundle
6811
+ * has the matching `DEVICE_TYPE_META` entry.
6812
+ */
6813
+ function deviceTypeMetaOf(type) {
6814
+ if (!isDeviceType(type)) return FALLBACK_DEVICE_TYPE_META;
6815
+ return DEVICE_TYPE_META[type] ?? FALLBACK_DEVICE_TYPE_META;
6816
+ }
6817
+ /** Runtime-string-safe role lookup; unknown roles fall back to `GenericSensor`. */
6818
+ function deviceRoleMetaOf(role) {
6819
+ if (!isDeviceRole(role)) return FALLBACK_DEVICE_ROLE_META;
6820
+ return DEVICE_ROLE_META[role] ?? FALLBACK_DEVICE_ROLE_META;
6821
+ }
6822
+ /**
6823
+ * Icons an operator may pick for a per-device `display.icon` override (and for
6824
+ * a per-role default), keyed by a stable kebab-case name. Named imports only —
6825
+ * no dynamic indexing into lucide, so the bundle carries exactly this set.
6826
+ * Unknown keys resolve to `undefined` (caller falls through to the next level).
6454
6827
  *
6455
- * Push-subscription methods (`kind: 'subscription'`) emit a typeof
6456
- * alias over `useSubscription`, so the call shape is
6457
- * `useToastOnToast(input, { onData: cb })` — same as the underlying
6458
- * tRPC hook. The eventBus-backed `useLiveEvent` (see
6459
- * `../hooks/use-live-event.ts`) covers events that don't ride a tRPC
6460
- * subscription; the two surfaces coexist by design.
6828
+ * The vocabulary is the one the VIEWER draws from too (its own icon module,
6829
+ * keyed by these same strings): a key added here that the phone cannot draw
6830
+ * falls through to the next resolution level there, which reads as "my change
6831
+ * did nothing". So the KEY is the contract — a stable kebab-case string, never
6832
+ * a glyph identity — and `device-meta.test.ts` pins that every key resolves
6833
+ * and that the set never loses a key an operator may already have persisted.
6834
+ *
6835
+ * Grouped by what an operator is naming: a device, a light or appliance, a
6836
+ * sensor or the weather it reads, a room, or what moves through it. `gauge2`
6837
+ * is a legacy alias kept for rows that persisted it.
6461
6838
  */
6462
- /** Generated alias around `trpc.accessories.setChildHidden.useMutation`. */
6463
- var useAccessoriesSetChildHidden = trpc.accessories.setChildHidden.useMutation;
6464
- /** Generated alias around `trpc.accessories.getStatus.useQuery`. */
6465
- var useAccessoriesGetStatus = trpc.accessories.getStatus.useQuery;
6466
- /** Generated alias around `trpc.addonPages.listPages.useQuery`. */
6467
- var useAddonPagesListPages = trpc.addonPages.listPages.useQuery;
6468
- /** Generated alias around `trpc.addons.list.useQuery`. */
6469
- var useAddonsList = trpc.addons.list.useQuery;
6470
- /** Generated alias around `trpc.addons.getLogs.useQuery`. */
6471
- var useAddonsGetLogs = trpc.addons.getLogs.useQuery;
6472
- /** Generated alias around `trpc.addons.listPackages.useQuery`. */
6473
- var useAddonsListPackages = trpc.addons.listPackages.useQuery;
6474
- /** Generated alias around `trpc.addons.installPackage.useMutation`. */
6475
- var useAddonsInstallPackage = trpc.addons.installPackage.useMutation;
6476
- /** Generated alias around `trpc.addons.installFromWorkspace.useMutation`. */
6477
- var useAddonsInstallFromWorkspace = trpc.addons.installFromWorkspace.useMutation;
6478
- /** Generated alias around `trpc.addons.isWorkspaceAvailable.useQuery`. */
6479
- var useAddonsIsWorkspaceAvailable = trpc.addons.isWorkspaceAvailable.useQuery;
6480
- /** Generated alias around `trpc.addons.listWorkspacePackages.useQuery`. */
6481
- var useAddonsListWorkspacePackages = trpc.addons.listWorkspacePackages.useQuery;
6482
- /** Generated alias around `trpc.addons.uninstallPackage.useMutation`. */
6483
- var useAddonsUninstallPackage = trpc.addons.uninstallPackage.useMutation;
6484
- /** Generated alias around `trpc.addons.listInstallations.useQuery`. */
6485
- var useAddonsListInstallations = trpc.addons.listInstallations.useQuery;
6486
- /** Generated alias around `trpc.addons.reloadPackages.useMutation`. */
6487
- var useAddonsReloadPackages = trpc.addons.reloadPackages.useMutation;
6488
- /** Generated alias around `trpc.addons.searchAvailable.useQuery`. */
6489
- var useAddonsSearchAvailable = trpc.addons.searchAvailable.useQuery;
6490
- /** Generated alias around `trpc.addons.listUpdates.useQuery`. */
6491
- var useAddonsListUpdates = trpc.addons.listUpdates.useQuery;
6492
- /** Generated alias around `trpc.addons.updatePackage.useMutation`. */
6493
- var useAddonsUpdatePackage = trpc.addons.updatePackage.useMutation;
6494
- /** Generated alias around `trpc.addons.rollbackPackage.useMutation`. */
6495
- var useAddonsRollbackPackage = trpc.addons.rollbackPackage.useMutation;
6496
- /** Generated alias around `trpc.addons.forceRefresh.useMutation`. */
6497
- var useAddonsForceRefresh = trpc.addons.forceRefresh.useMutation;
6498
- /** Generated alias around `trpc.addons.restartServer.useMutation`. */
6499
- var useAddonsRestartServer = trpc.addons.restartServer.useMutation;
6500
- /** Generated alias around `trpc.addons.getLastRestart.useQuery`. */
6501
- var useAddonsGetLastRestart = trpc.addons.getLastRestart.useQuery;
6502
- /** Generated alias around `trpc.addons.listFrameworkPackages.useQuery`. */
6503
- var useAddonsListFrameworkPackages = trpc.addons.listFrameworkPackages.useQuery;
6504
- /** Generated alias around `trpc.addons.listCapabilityProviders.useQuery`. */
6505
- var useAddonsListCapabilityProviders = trpc.addons.listCapabilityProviders.useQuery;
6506
- /** Generated alias around `trpc.addons.setCapabilityProviderEnabled.useMutation`. */
6507
- var useAddonsSetCapabilityProviderEnabled = trpc.addons.setCapabilityProviderEnabled.useMutation;
6508
- /** Generated alias around `trpc.addons.getVersions.useQuery`. */
6509
- var useAddonsGetVersions = trpc.addons.getVersions.useQuery;
6510
- /** Generated alias around `trpc.addons.restartAddon.useMutation`. */
6511
- var useAddonsRestartAddon = trpc.addons.restartAddon.useMutation;
6512
- /** Generated alias around `trpc.addons.retryLoad.useMutation`. */
6513
- var useAddonsRetryLoad = trpc.addons.retryLoad.useMutation;
6514
- /** Generated alias around `trpc.addons.getAutoUpdateSettings.useQuery`. */
6515
- var useAddonsGetAutoUpdateSettings = trpc.addons.getAutoUpdateSettings.useQuery;
6516
- /** Generated alias around `trpc.addons.setAutoUpdateSettings.useMutation`. */
6517
- var useAddonsSetAutoUpdateSettings = trpc.addons.setAutoUpdateSettings.useMutation;
6518
- /** Generated alias around `trpc.addons.getAddonAutoUpdate.useQuery`. */
6519
- var useAddonsGetAddonAutoUpdate = trpc.addons.getAddonAutoUpdate.useQuery;
6520
- /** Generated alias around `trpc.addons.setAddonAutoUpdate.useMutation`. */
6521
- var useAddonsSetAddonAutoUpdate = trpc.addons.setAddonAutoUpdate.useMutation;
6522
- /** Generated alias around `trpc.addons.applyAutoUpdateToAll.useMutation`. */
6523
- var useAddonsApplyAutoUpdateToAll = trpc.addons.applyAutoUpdateToAll.useMutation;
6524
- /** Generated alias around `trpc.addons.custom.useMutation`. */
6525
- var useAddonsCustom = trpc.addons.custom.useMutation;
6526
- /** Generated alias around `trpc.addons.startJob.useMutation`. */
6527
- var useAddonsStartJob = trpc.addons.startJob.useMutation;
6528
- /** Generated alias around `trpc.addons.getJob.useQuery`. */
6529
- var useAddonsGetJob = trpc.addons.getJob.useQuery;
6530
- /** Generated alias around `trpc.addons.listJobs.useQuery`. */
6531
- var useAddonsListJobs = trpc.addons.listJobs.useQuery;
6532
- /** Generated alias around `trpc.addons.cancelJob.useMutation`. */
6533
- var useAddonsCancelJob = trpc.addons.cancelJob.useMutation;
6534
- /** Generated alias around `trpc.addons.onAddonLogs.useSubscription`. */
6535
- var useAddonsOnAddonLogs = trpc.addons.onAddonLogs.useSubscription;
6536
- /** Generated alias around `trpc.addonSettings.getGlobalSettings.useQuery`. */
6537
- var useAddonSettingsGetGlobalSettings = trpc.addonSettings.getGlobalSettings.useQuery;
6538
- /** Generated alias around `trpc.addonSettings.updateGlobalSettings.useMutation`. */
6539
- var useAddonSettingsUpdateGlobalSettings = trpc.addonSettings.updateGlobalSettings.useMutation;
6540
- /** Generated alias around `trpc.addonSettings.getIntegrationSettings.useQuery`. */
6541
- var useAddonSettingsGetIntegrationSettings = trpc.addonSettings.getIntegrationSettings.useQuery;
6542
- /** Generated alias around `trpc.addonSettings.getDeviceSettings.useQuery`. */
6543
- var useAddonSettingsGetDeviceSettings = trpc.addonSettings.getDeviceSettings.useQuery;
6544
- /** Generated alias around `trpc.addonSettings.updateDeviceSettings.useMutation`. */
6545
- var useAddonSettingsUpdateDeviceSettings = trpc.addonSettings.updateDeviceSettings.useMutation;
6546
- /** Generated alias around `trpc.addonWidgets.listWidgets.useQuery`. */
6547
- var useAddonWidgetsListWidgets = trpc.addonWidgets.listWidgets.useQuery;
6548
- /** Generated alias around `trpc.airQualitySensor.getStatus.useQuery`. */
6549
- var useAirQualitySensorGetStatus = trpc.airQualitySensor.getStatus.useQuery;
6550
- /** Generated alias around `trpc.alarmPanel.arm.useMutation`. */
6551
- var useAlarmPanelArm = trpc.alarmPanel.arm.useMutation;
6552
- /** Generated alias around `trpc.alarmPanel.disarm.useMutation`. */
6553
- var useAlarmPanelDisarm = trpc.alarmPanel.disarm.useMutation;
6554
- /** Generated alias around `trpc.alarmPanel.trigger.useMutation`. */
6555
- var useAlarmPanelTrigger = trpc.alarmPanel.trigger.useMutation;
6556
- /** Generated alias around `trpc.alarmPanel.getStatus.useQuery`. */
6557
- var useAlarmPanelGetStatus = trpc.alarmPanel.getStatus.useQuery;
6558
- /** Generated alias around `trpc.alerts.emit.useMutation`. */
6559
- var useAlertsEmit = trpc.alerts.emit.useMutation;
6560
- /** Generated alias around `trpc.alerts.update.useMutation`. */
6561
- var useAlertsUpdate = trpc.alerts.update.useMutation;
6562
- /** Generated alias around `trpc.alerts.list.useQuery`. */
6563
- var useAlertsList = trpc.alerts.list.useQuery;
6564
- /** Generated alias around `trpc.alerts.getUnreadCount.useQuery`. */
6565
- var useAlertsGetUnreadCount = trpc.alerts.getUnreadCount.useQuery;
6566
- /** Generated alias around `trpc.alerts.markRead.useMutation`. */
6567
- var useAlertsMarkRead = trpc.alerts.markRead.useMutation;
6568
- /** Generated alias around `trpc.alerts.markAllRead.useMutation`. */
6569
- var useAlertsMarkAllRead = trpc.alerts.markAllRead.useMutation;
6570
- /** Generated alias around `trpc.alerts.dismiss.useMutation`. */
6571
- var useAlertsDismiss = trpc.alerts.dismiss.useMutation;
6572
- /** Generated alias around `trpc.ambientLightSensor.getStatus.useQuery`. */
6573
- var useAmbientLightSensorGetStatus = trpc.ambientLightSensor.getStatus.useQuery;
6574
- /** Generated alias around `trpc.audioAnalysis.resolveDeviceSettings.useQuery`. */
6575
- var useAudioAnalysisResolveDeviceSettings = trpc.audioAnalysis.resolveDeviceSettings.useQuery;
6576
- /** Generated alias around `trpc.audioAnalysis.getDeviceSettingsContribution.useQuery`. */
6577
- var useAudioAnalysisGetDeviceSettingsContribution = trpc.audioAnalysis.getDeviceSettingsContribution.useQuery;
6578
- /** Generated alias around `trpc.audioAnalysis.getDeviceLiveContribution.useQuery`. */
6839
+ var DISPLAY_ICON_REGISTRY = {
6840
+ camera: Camera,
6841
+ network: Network,
6842
+ "toggle-left": ToggleLeft,
6843
+ "toggle-right": ToggleRight,
6844
+ "circle-dot": CircleDot,
6845
+ "refresh-cw": RefreshCw,
6846
+ box: Box,
6847
+ "scroll-text": ScrollText,
6848
+ workflow: Workflow,
6849
+ lock: Lock,
6850
+ "lock-open": LockOpen,
6851
+ blinds: Blinds,
6852
+ "air-vent": AirVent,
6853
+ "monitor-play": MonitorPlay,
6854
+ "sliders-horizontal": SlidersHorizontal,
6855
+ "map-pin": MapPin,
6856
+ bot: Bot,
6857
+ tractor: Tractor,
6858
+ image: Image,
6859
+ bone: Bone,
6860
+ "door-closed": DoorClosed,
6861
+ "door-open": DoorOpen,
6862
+ crosshair: Crosshair,
6863
+ eye: Eye,
6864
+ "eye-off": EyeOff,
6865
+ smartphone: Smartphone,
6866
+ phone: Phone,
6867
+ tablet: Tablet,
6868
+ laptop: Laptop,
6869
+ monitor: Monitor,
6870
+ tv: Tv,
6871
+ speaker: Speaker,
6872
+ printer: Printer,
6873
+ projector: Projector,
6874
+ "gamepad-2": Gamepad2,
6875
+ radio: Radio,
6876
+ router: Router,
6877
+ bluetooth: Bluetooth,
6878
+ cpu: Cpu,
6879
+ "hard-drive": HardDrive,
6880
+ server: Server,
6881
+ mic: Mic,
6882
+ "mic-off": MicOff,
6883
+ video: Video,
6884
+ "video-off": VideoOff,
6885
+ music: Music,
6886
+ plug: Plug,
6887
+ "plug-zap": PlugZap,
6888
+ power: Power,
6889
+ "power-off": PowerOff,
6890
+ cable: Cable,
6891
+ battery: Battery,
6892
+ "battery-charging": BatteryCharging,
6893
+ "battery-full": BatteryFull,
6894
+ "battery-low": BatteryLow,
6895
+ key: Key,
6896
+ "key-round": KeyRound,
6897
+ mailbox: Mailbox,
6898
+ package: Package,
6899
+ shield: Shield,
6900
+ "shield-check": ShieldCheck,
6901
+ "shield-off": ShieldOff,
6902
+ lightbulb: Lightbulb,
6903
+ lamp: Lamp,
6904
+ "lamp-ceiling": LampCeiling,
6905
+ "lamp-desk": LampDesk,
6906
+ "lamp-floor": LampFloor,
6907
+ flashlight: Flashlight,
6908
+ heater: Heater,
6909
+ snowflake: Snowflake,
6910
+ "thermometer-sun": ThermometerSun,
6911
+ "thermometer-snowflake": ThermometerSnowflake,
6912
+ refrigerator: Refrigerator,
6913
+ microwave: Microwave,
6914
+ "washing-machine": WashingMachine,
6915
+ "cooking-pot": CookingPot,
6916
+ utensils: Utensils,
6917
+ thermometer: Thermometer,
6918
+ droplets: Droplets,
6919
+ droplet: Droplet,
6920
+ gauge: Gauge,
6921
+ gauge2: Gauge,
6922
+ sun: Sun,
6923
+ "sun-medium": SunMedium,
6924
+ sunrise: Sunrise,
6925
+ moon: Moon,
6926
+ wind: Wind,
6927
+ cloud: Cloud,
6928
+ "cloud-rain": CloudRain,
6929
+ "cloud-sun": CloudSun,
6930
+ waves: Waves,
6931
+ sprout: Sprout,
6932
+ leaf: Leaf,
6933
+ flower: Flower,
6934
+ zap: Zap,
6935
+ flame: Flame,
6936
+ bell: Bell,
6937
+ "bell-ring": BellRing,
6938
+ "bell-off": BellOff,
6939
+ siren: Siren,
6940
+ radar: Radar,
6941
+ activity: Activity,
6942
+ hash: Hash,
6943
+ clock: Clock,
6944
+ timer: Timer,
6945
+ calendar: Calendar,
6946
+ pipette: Pipette,
6947
+ fan: Fan,
6948
+ bath: Bath,
6949
+ footprints: Footprints,
6950
+ "alarm-smoke": AlarmSmoke,
6951
+ "shield-alert": ShieldAlert,
6952
+ vibrate: Vibrate,
6953
+ wifi: Wifi,
6954
+ "wifi-off": WifiOff,
6955
+ "volume-2": Volume2,
6956
+ "volume-x": VolumeX,
6957
+ heart: Heart,
6958
+ "heart-pulse": HeartPulse,
6959
+ star: Star,
6960
+ sparkles: Sparkles,
6961
+ house: House,
6962
+ sofa: Sofa,
6963
+ "bed-double": BedDouble,
6964
+ bed: Bed,
6965
+ warehouse: Warehouse,
6966
+ trees: Trees,
6967
+ "tree-pine": TreePine,
6968
+ fence: Fence,
6969
+ store: Store,
6970
+ building: Building,
6971
+ "building-2": Building2,
6972
+ mountain: Mountain,
6973
+ umbrella: Umbrella,
6974
+ dumbbell: Dumbbell,
6975
+ wrench: Wrench,
6976
+ hammer: Hammer,
6977
+ drill: Drill,
6978
+ "waves-ladder": WavesLadder,
6979
+ "circle-parking": CircleParking,
6980
+ briefcase: Briefcase,
6981
+ route: Route,
6982
+ car: Car,
6983
+ bike: Bike,
6984
+ bus: Bus,
6985
+ truck: Truck,
6986
+ plane: Plane,
6987
+ "train-front": TrainFront,
6988
+ sailboat: Sailboat,
6989
+ dog: Dog,
6990
+ cat: Cat,
6991
+ bird: Bird,
6992
+ "paw-print": PawPrint,
6993
+ baby: Baby,
6994
+ user: User,
6995
+ users: Users,
6996
+ "person-standing": PersonStanding
6997
+ };
6998
+ /** Resolve a `display.icon` name to a lucide component, or `undefined` when the
6999
+ * name is not in the curated `DISPLAY_ICON_REGISTRY`. */
7000
+ function resolveDisplayIcon(name) {
7001
+ return Object.hasOwn(DISPLAY_ICON_REGISTRY, name) ? DISPLAY_ICON_REGISTRY[name] : void 0;
7002
+ }
7003
+ /**
7004
+ * EXHAUSTIVE device-type filter options — one entry per `DeviceType` member,
7005
+ * derived from `DEVICE_TYPE_META` so the set is always complete and a newly
7006
+ * added `DeviceType` automatically appears in every type filter (Devices page,
7007
+ * adoption modal, …) without touching any consumer.
7008
+ *
7009
+ * The previous per-consumer logic built options from the types PRESENT in the
7010
+ * list (or a hand-maintained subset), so recently-added types (vacuum, lawn
7011
+ * mower, valve, humidifier, water heater, weather, image, …) were silently
7012
+ * missing from the dropdown. This is the single source of truth instead.
7013
+ *
7014
+ * `id` is the raw `DeviceType` string (matches the filtering predicate, which
7015
+ * compares against `device.type`); `label` + `icon` come from the meta map.
7016
+ * Ordered by `DeviceType` declaration order for a stable, intentional layout.
7017
+ */
7018
+ var ALL_DEVICE_TYPE_FILTER_OPTIONS = Object.values(DeviceType).map((type) => {
7019
+ const meta = deviceTypeMetaOf(type);
7020
+ return {
7021
+ id: type,
7022
+ label: meta.label,
7023
+ icon: meta.icon
7024
+ };
7025
+ });
7026
+ function allDeviceTypeFilterOptions() {
7027
+ return ALL_DEVICE_TYPE_FILTER_OPTIONS;
7028
+ }
7029
+ //#endregion
7030
+ //#region src/composites/icon-picker.tsx
7031
+ /**
7032
+ * icon-picker — the ONE control for choosing an icon, wherever admin-ui asks.
7033
+ *
7034
+ * There were two: a device one over `DISPLAY_ICON_REGISTRY` and a room one
7035
+ * over `LOCATION_ICON_IDS`, identical apart from the vocabulary they walked.
7036
+ * That was tolerable while the device set was 56 glyphs in a flat grid. It is
7037
+ * not at 157: a picker that only lets the operator HUNT is a picker they stop
7038
+ * using, and the two copies were already drifting — only one of them had an
7039
+ * i18n'd label.
7040
+ *
7041
+ * So the vocabulary, the grouping and the search all come from
7042
+ * `@camstack/types`' `display-icon-vocabulary`, which is the same module the
7043
+ * viewer's sheet searches. What is left here is the web half: a popover, a
7044
+ * grid, and a text field.
7045
+ *
7046
+ * ## What it does NOT own
7047
+ *
7048
+ * - **The glyphs.** `resolveGlyph` is a prop, because this package's table is
7049
+ * `lucide-react` and the viewer's is `lucide-react-native`. A key this build
7050
+ * cannot draw is SKIPPED rather than rendered as a hole — an empty tile
7051
+ * reads as a rendering bug, not as a missing choice.
7052
+ * - **The words.** Every string is a prop. This package has no i18n (one file
7053
+ * in it imports react-i18next, and that is the exception), and a control
7054
+ * that reached for a translation would force it on every consumer.
7055
+ * - **The write.** `onPick` is called and that is all; whether it is a form
7056
+ * value or an immediate mutation is the caller's business, as it already was
7057
+ * in both pickers this replaces.
7058
+ *
7059
+ * ## The clear tile is never filtered away
7060
+ *
7061
+ * The "inherit" / "no icon" choice sits above the sections and ignores the
7062
+ * search. A query that hid it would strand the operator on a glyph they picked
7063
+ * by accident with no way back to the default — the one action a picker must
7064
+ * always offer.
7065
+ */
7066
+ function IconPicker({ value, onPick, resolveGlyph, fallbackGlyph, labels, only, extraWords, clearGlyph, labelFor, disabled = false }) {
7067
+ const [open, setOpen] = useState(false);
7068
+ const [query, setQuery] = useState("");
7069
+ const allowed = useMemo(() => only === void 0 ? null : new Set(only), [only]);
7070
+ const sections = useMemo(() => {
7071
+ const hits = searchDisplayIcons(query, extraWords);
7072
+ const byGroup = /* @__PURE__ */ new Map();
7073
+ for (const hit of hits) {
7074
+ const { key, group } = hit.entry;
7075
+ if (allowed !== null && !allowed.has(key)) continue;
7076
+ if (resolveGlyph(key) === void 0) continue;
7077
+ const bucket = byGroup.get(group);
7078
+ if (bucket === void 0) byGroup.set(group, [key]);
7079
+ else bucket.push(key);
7080
+ }
7081
+ return DISPLAY_ICON_GROUPS.flatMap((group) => {
7082
+ const keys = byGroup.get(group);
7083
+ return keys === void 0 || keys.length === 0 ? [] : [{
7084
+ group,
7085
+ keys
7086
+ }];
7087
+ });
7088
+ }, [
7089
+ query,
7090
+ extraWords,
7091
+ allowed,
7092
+ resolveGlyph
7093
+ ]);
7094
+ const Current = (value === null ? void 0 : resolveGlyph(value)) ?? fallbackGlyph;
7095
+ const choose = async (next) => {
7096
+ setOpen(false);
7097
+ setQuery("");
7098
+ await onPick(next);
7099
+ };
7100
+ const Clear = clearGlyph ?? fallbackGlyph;
7101
+ return /* @__PURE__ */ jsxs("div", {
7102
+ className: "relative flex shrink-0 items-center gap-2",
7103
+ children: [/* @__PURE__ */ jsx("button", {
7104
+ type: "button",
7105
+ disabled,
7106
+ onClick: () => setOpen((o) => !o),
7107
+ "aria-expanded": open,
7108
+ "aria-label": labels.trigger,
7109
+ title: labels.trigger,
7110
+ className: "rounded border border-border p-1 text-foreground-muted hover:bg-surface-hover disabled:opacity-50",
7111
+ children: /* @__PURE__ */ jsx(Current, { className: "h-4 w-4" })
7112
+ }), open && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("button", {
7113
+ type: "button",
7114
+ "aria-label": labels.close,
7115
+ onClick: () => setOpen(false),
7116
+ className: "fixed inset-0 z-10 cursor-default"
7117
+ }), /* @__PURE__ */ jsxs("div", {
7118
+ role: "group",
7119
+ "aria-label": labels.panel,
7120
+ className: "absolute left-0 top-full z-20 mt-1 max-h-80 w-72 overflow-y-auto rounded-md border border-border bg-surface p-2 shadow-lg",
7121
+ children: [
7122
+ /* @__PURE__ */ jsx("input", {
7123
+ type: "search",
7124
+ value: query,
7125
+ autoFocus: true,
7126
+ onChange: (e) => setQuery(e.target.value),
7127
+ placeholder: labels.searchPlaceholder,
7128
+ "aria-label": labels.searchPlaceholder,
7129
+ className: "mb-2 w-full rounded border border-border bg-surface-raised px-2 py-1 text-xs text-foreground placeholder:text-foreground-subtle"
7130
+ }),
7131
+ /* @__PURE__ */ jsxs("button", {
7132
+ type: "button",
7133
+ onClick: () => void choose(null),
7134
+ "aria-pressed": value === null,
7135
+ className: "mb-2 flex w-full items-center gap-1.5 rounded px-1.5 py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover",
7136
+ children: [/* @__PURE__ */ jsx(Clear, { className: "h-3.5 w-3.5" }), labels.clear]
7137
+ }),
7138
+ sections.length === 0 ? /* @__PURE__ */ jsx("p", {
7139
+ className: "px-1.5 py-2 text-[11px] text-foreground-subtle",
7140
+ children: labels.noMatches
7141
+ }) : sections.map(({ group, keys }) => /* @__PURE__ */ jsxs("section", {
7142
+ className: "mb-2 last:mb-0",
7143
+ children: [/* @__PURE__ */ jsx("h4", {
7144
+ className: "px-1.5 pb-1 text-[10px] uppercase tracking-wide text-foreground-subtle",
7145
+ children: labels.groups[group]
7146
+ }), /* @__PURE__ */ jsx("div", {
7147
+ className: "grid grid-cols-7 gap-1",
7148
+ children: keys.map((key) => {
7149
+ const Glyph = resolveGlyph(key);
7150
+ if (Glyph === void 0) return null;
7151
+ const selected = value === key;
7152
+ return /* @__PURE__ */ jsx("button", {
7153
+ type: "button",
7154
+ onClick: () => void choose(key),
7155
+ "aria-pressed": selected,
7156
+ "aria-label": labelFor?.(key) ?? key,
7157
+ title: labelFor?.(key) ?? key,
7158
+ className: selected ? "flex items-center justify-center rounded border border-primary bg-primary/10 p-1.5 text-foreground" : "flex items-center justify-center rounded border border-transparent p-1.5 text-foreground-muted hover:bg-surface-hover",
7159
+ children: /* @__PURE__ */ jsx(Glyph, { className: "h-4 w-4" })
7160
+ }, key);
7161
+ })
7162
+ })]
7163
+ }, group))
7164
+ ]
7165
+ })] })]
7166
+ });
7167
+ }
7168
+ //#endregion
7169
+ //#region src/composites/config-icon-field.tsx
7170
+ /**
7171
+ * The control behind a `type: 'icon'` settings field (D780).
7172
+ *
7173
+ * The shared `<IconPicker>` over the display-icon vocabulary, with the device
7174
+ * glyph table — the same control and the same keys the device and room
7175
+ * pickers use. An icon field always draws something, so clearing is "back to
7176
+ * the field's default", and a stored value the vocabulary does not know (a
7177
+ * free-text lucide name from before D780) is drawn as that default.
7178
+ */
7179
+ function labelsFor(label) {
7180
+ return {
7181
+ trigger: `Choose an icon for ${label}`,
7182
+ panel: `${label} icon`,
7183
+ close: "Close",
7184
+ searchPlaceholder: "Search icons",
7185
+ clear: "Default icon",
7186
+ noMatches: "No icon matches",
7187
+ groups: {
7188
+ device: "Devices",
7189
+ lighting: "Lighting",
7190
+ sensing: "Sensing",
7191
+ place: "Places",
7192
+ life: "Life"
7193
+ }
7194
+ };
7195
+ }
7196
+ function ConfigIconFieldControl({ label, value, defaultKey, disabled, onChange }) {
7197
+ const key = resolveDisplayIconKey(value, defaultKey);
7198
+ const DefaultGlyph = resolveDisplayIcon(defaultKey) ?? ShieldCheck;
7199
+ return /* @__PURE__ */ jsxs("div", {
7200
+ className: "flex items-center gap-2",
7201
+ children: [/* @__PURE__ */ jsx(IconPicker, {
7202
+ value: key === "" ? null : key,
7203
+ onPick: (next) => onChange(next ?? defaultKey),
7204
+ resolveGlyph: resolveDisplayIcon,
7205
+ fallbackGlyph: DefaultGlyph,
7206
+ labels: labelsFor(label),
7207
+ disabled
7208
+ }), /* @__PURE__ */ jsx("span", {
7209
+ className: "text-[11px] text-foreground-subtle font-mono",
7210
+ children: key
7211
+ })]
7212
+ });
7213
+ }
7214
+ //#endregion
7215
+ //#region src/composites/config-secret-field.tsx
7216
+ /**
7217
+ * A keychain-stored secret in a settings form (D787, D789).
7218
+ *
7219
+ * The form never holds the value. It receives the field's VIEW —
7220
+ * `{ $secret, v, set: true }` when something is stored, `null` when nothing
7221
+ * is — and sends back exactly one of three things:
7222
+ *
7223
+ * - the view, unchanged ........ KEEP (also what an untouched form saves);
7224
+ * - a non-empty typed string ... CHANGE (the owner stores it in the keychain);
7225
+ * - `{ $secretClear: true }` ... CLEAR (the only thing that removes it).
7226
+ *
7227
+ * `''` is never sent for "remove": the save path reads it as KEEP, because an
7228
+ * empty string is what a defaults fallback writes back.
7229
+ *
7230
+ * Reveal shows the stored value for a few seconds, through the host's audited
7231
+ * `keychain-admin.reveal` (provided with {@link SecretRevealProvider}); a host
7232
+ * that provides none has no Reveal button.
7233
+ */
7234
+ /** How long a revealed value stays on screen. */
7235
+ var SECRET_REVEAL_MS = 1e4;
7236
+ var SecretRevealContext = createSharedContext("camstack:secret-reveal", null);
7237
+ function SecretRevealProvider({ reveal, children }) {
7238
+ return /* @__PURE__ */ jsx(SecretRevealContext.Provider, {
7239
+ value: reveal,
7240
+ children
7241
+ });
7242
+ }
7243
+ /** Does this value belong to the keychain rendering (a view, or "not set")? */
7244
+ function isKeychainFieldValue(value) {
7245
+ return value === null || isSecretFieldView(value) || isSecretClear(value);
7246
+ }
7247
+ var BUTTON$1 = "inline-flex items-center gap-1 rounded border border-border bg-surface px-2 py-0.5 text-[11px] font-medium text-foreground hover:border-primary/40 hover:bg-primary/5 disabled:opacity-40 disabled:cursor-not-allowed";
7248
+ var INPUT = "w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-xs text-foreground focus:border-primary focus:ring-1 focus:ring-primary/30 outline-none disabled:opacity-50";
7249
+ function SecretFieldControl({ field, value, onChange, disabled, multiline }) {
7250
+ const reveal = useContext(SecretRevealContext);
7251
+ const [original] = useState(() => isSecretFieldView(value) ? value : null);
7252
+ const [mode, setMode] = useState(() => typeof value === "string" && value.length > 0 ? {
7253
+ kind: "editing",
7254
+ text: value
7255
+ } : isSecretClear(value) ? { kind: "clearing" } : { kind: "idle" });
7256
+ const [shown, setShown] = useState(null);
7257
+ const [revealError, setRevealError] = useState(null);
7258
+ const [showTyped, setShowTyped] = useState(false);
7259
+ useEffect(() => {
7260
+ if (shown === null) return;
7261
+ const timer = setTimeout(() => setShown(null), SECRET_REVEAL_MS);
7262
+ return () => clearTimeout(timer);
7263
+ }, [shown]);
7264
+ const isSet = original !== null;
7265
+ const ref = original?.$secret ?? null;
7266
+ const keep = () => {
7267
+ setMode({ kind: "idle" });
7268
+ onChange(original);
7269
+ };
7270
+ const label = "label" in field ? field.label : "";
7271
+ return /* @__PURE__ */ jsxs("div", {
7272
+ className: "space-y-1",
7273
+ "data-secret-field": "key" in field ? field.key : void 0,
7274
+ children: [
7275
+ mode.kind === "editing" ? /* @__PURE__ */ jsxs("div", {
7276
+ className: "flex items-start gap-1.5",
7277
+ children: [/* @__PURE__ */ jsxs("div", {
7278
+ className: "relative flex-1",
7279
+ children: [multiline ? /* @__PURE__ */ jsx("textarea", {
7280
+ className: `${INPUT} min-h-[60px] font-mono`,
7281
+ "aria-label": label,
7282
+ value: mode.text,
7283
+ autoComplete: "off",
7284
+ spellCheck: false,
7285
+ disabled,
7286
+ onChange: (e) => {
7287
+ setMode({
7288
+ kind: "editing",
7289
+ text: e.target.value
7290
+ });
7291
+ onChange(e.target.value.length > 0 ? e.target.value : original);
7292
+ }
7293
+ }) : /* @__PURE__ */ jsx("input", {
7294
+ type: showTyped ? "text" : "password",
7295
+ className: `${INPUT} pr-8`,
7296
+ "aria-label": label,
7297
+ value: mode.text,
7298
+ autoComplete: "new-password",
7299
+ spellCheck: false,
7300
+ disabled,
7301
+ autoFocus: true,
7302
+ onChange: (e) => {
7303
+ setMode({
7304
+ kind: "editing",
7305
+ text: e.target.value
7306
+ });
7307
+ onChange(e.target.value.length > 0 ? e.target.value : original);
7308
+ }
7309
+ }), !multiline && /* @__PURE__ */ jsx("button", {
7310
+ type: "button",
7311
+ className: "absolute right-2 top-1/2 -translate-y-1/2 text-foreground-subtle hover:text-foreground",
7312
+ tabIndex: -1,
7313
+ "aria-label": showTyped ? "Hide" : "Show",
7314
+ onClick: () => setShowTyped((s) => !s),
7315
+ children: showTyped ? /* @__PURE__ */ jsx(EyeOff, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Eye, { className: "h-3.5 w-3.5" })
7316
+ })]
7317
+ }), /* @__PURE__ */ jsx("button", {
7318
+ type: "button",
7319
+ className: BUTTON$1,
7320
+ onClick: keep,
7321
+ disabled,
7322
+ children: "Cancel"
7323
+ })]
7324
+ }) : /* @__PURE__ */ jsxs("div", {
7325
+ className: "flex flex-wrap items-center gap-1.5",
7326
+ children: [/* @__PURE__ */ jsxs("span", {
7327
+ className: `inline-flex items-center gap-1 rounded-md border px-2 py-1 text-xs ${mode.kind === "clearing" ? "border-danger/40 text-danger" : isSet ? "border-border text-foreground" : "border-dashed border-border text-foreground-subtle"}`,
7328
+ children: [/* @__PURE__ */ jsx(KeyRound, { className: "h-3 w-3" }), mode.kind === "clearing" ? "will be removed on save" : shown !== null ? /* @__PURE__ */ jsx("code", {
7329
+ className: "font-mono break-all",
7330
+ children: shown
7331
+ }) : isSet ? "●●●●●● set" : "not set"]
7332
+ }), mode.kind === "clearing" ? /* @__PURE__ */ jsx("button", {
7333
+ type: "button",
7334
+ className: BUTTON$1,
7335
+ onClick: keep,
7336
+ disabled,
7337
+ children: "Undo"
7338
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
7339
+ /* @__PURE__ */ jsx("button", {
7340
+ type: "button",
7341
+ className: BUTTON$1,
7342
+ disabled,
7343
+ onClick: () => setMode({
7344
+ kind: "editing",
7345
+ text: ""
7346
+ }),
7347
+ children: isSet ? "Change" : "Set"
7348
+ }),
7349
+ isSet && /* @__PURE__ */ jsx("button", {
7350
+ type: "button",
7351
+ className: BUTTON$1,
7352
+ disabled,
7353
+ onClick: () => {
7354
+ setShown(null);
7355
+ setMode({ kind: "clearing" });
7356
+ onChange(SECRET_CLEAR);
7357
+ },
7358
+ children: "Clear"
7359
+ }),
7360
+ isSet && ref !== null && reveal !== null && /* @__PURE__ */ jsx("button", {
7361
+ type: "button",
7362
+ className: BUTTON$1,
7363
+ disabled,
7364
+ onClick: () => {
7365
+ if (shown !== null) {
7366
+ setShown(null);
7367
+ return;
7368
+ }
7369
+ setRevealError(null);
7370
+ reveal(ref).then(setShown, (err) => setRevealError(err instanceof Error ? err.message : String(err)));
7371
+ },
7372
+ children: shown !== null ? "Hide" : "Reveal"
7373
+ })
7374
+ ] })]
7375
+ }),
7376
+ original?.inline === true && mode.kind === "idle" && /* @__PURE__ */ jsx("p", {
7377
+ className: "text-[10px] text-foreground-subtle",
7378
+ children: "Stored before the keychain; it moves there the next time it is changed."
7379
+ }),
7380
+ revealError !== null && /* @__PURE__ */ jsx("p", {
7381
+ className: "text-[10px] text-danger",
7382
+ children: revealError
7383
+ })
7384
+ ]
7385
+ });
7386
+ }
7387
+ //#endregion
7388
+ //#region src/generated/system-hooks.ts
7389
+ /**
7390
+ * Per-method React-Query hooks for every `scope: 'system'` capability.
7391
+ *
7392
+ * Each hook is a thin pass-through to `trpc.<cap>.<method>.useQuery` /
7393
+ * `.useMutation` / `.useSubscription` so the typed inference + RQ
7394
+ * cache/dedup keep working — the generator only contributes a consistent
7395
+ * name and stable export surface so admin-ui pages can write
7396
+ * `useAddonsList()` instead of `trpc.addons.list.useQuery()`.
7397
+ *
7398
+ * Push-subscription methods (`kind: 'subscription'`) emit a typeof
7399
+ * alias over `useSubscription`, so the call shape is
7400
+ * `useToastOnToast(input, { onData: cb })` — same as the underlying
7401
+ * tRPC hook. The eventBus-backed `useLiveEvent` (see
7402
+ * `../hooks/use-live-event.ts`) covers events that don't ride a tRPC
7403
+ * subscription; the two surfaces coexist by design.
7404
+ */
7405
+ /** Generated alias around `trpc.accessories.setChildHidden.useMutation`. */
7406
+ var useAccessoriesSetChildHidden = trpc.accessories.setChildHidden.useMutation;
7407
+ /** Generated alias around `trpc.accessories.getStatus.useQuery`. */
7408
+ var useAccessoriesGetStatus = trpc.accessories.getStatus.useQuery;
7409
+ /** Generated alias around `trpc.addonPages.listPages.useQuery`. */
7410
+ var useAddonPagesListPages = trpc.addonPages.listPages.useQuery;
7411
+ /** Generated alias around `trpc.addons.list.useQuery`. */
7412
+ var useAddonsList = trpc.addons.list.useQuery;
7413
+ /** Generated alias around `trpc.addons.getLogs.useQuery`. */
7414
+ var useAddonsGetLogs = trpc.addons.getLogs.useQuery;
7415
+ /** Generated alias around `trpc.addons.listPackages.useQuery`. */
7416
+ var useAddonsListPackages = trpc.addons.listPackages.useQuery;
7417
+ /** Generated alias around `trpc.addons.installPackage.useMutation`. */
7418
+ var useAddonsInstallPackage = trpc.addons.installPackage.useMutation;
7419
+ /** Generated alias around `trpc.addons.installFromWorkspace.useMutation`. */
7420
+ var useAddonsInstallFromWorkspace = trpc.addons.installFromWorkspace.useMutation;
7421
+ /** Generated alias around `trpc.addons.isWorkspaceAvailable.useQuery`. */
7422
+ var useAddonsIsWorkspaceAvailable = trpc.addons.isWorkspaceAvailable.useQuery;
7423
+ /** Generated alias around `trpc.addons.listWorkspacePackages.useQuery`. */
7424
+ var useAddonsListWorkspacePackages = trpc.addons.listWorkspacePackages.useQuery;
7425
+ /** Generated alias around `trpc.addons.uninstallPackage.useMutation`. */
7426
+ var useAddonsUninstallPackage = trpc.addons.uninstallPackage.useMutation;
7427
+ /** Generated alias around `trpc.addons.listInstallations.useQuery`. */
7428
+ var useAddonsListInstallations = trpc.addons.listInstallations.useQuery;
7429
+ /** Generated alias around `trpc.addons.reloadPackages.useMutation`. */
7430
+ var useAddonsReloadPackages = trpc.addons.reloadPackages.useMutation;
7431
+ /** Generated alias around `trpc.addons.searchAvailable.useQuery`. */
7432
+ var useAddonsSearchAvailable = trpc.addons.searchAvailable.useQuery;
7433
+ /** Generated alias around `trpc.addons.listUpdates.useQuery`. */
7434
+ var useAddonsListUpdates = trpc.addons.listUpdates.useQuery;
7435
+ /** Generated alias around `trpc.addons.updatePackage.useMutation`. */
7436
+ var useAddonsUpdatePackage = trpc.addons.updatePackage.useMutation;
7437
+ /** Generated alias around `trpc.addons.rollbackPackage.useMutation`. */
7438
+ var useAddonsRollbackPackage = trpc.addons.rollbackPackage.useMutation;
7439
+ /** Generated alias around `trpc.addons.forceRefresh.useMutation`. */
7440
+ var useAddonsForceRefresh = trpc.addons.forceRefresh.useMutation;
7441
+ /** Generated alias around `trpc.addons.restartServer.useMutation`. */
7442
+ var useAddonsRestartServer = trpc.addons.restartServer.useMutation;
7443
+ /** Generated alias around `trpc.addons.getLastRestart.useQuery`. */
7444
+ var useAddonsGetLastRestart = trpc.addons.getLastRestart.useQuery;
7445
+ /** Generated alias around `trpc.addons.listFrameworkPackages.useQuery`. */
7446
+ var useAddonsListFrameworkPackages = trpc.addons.listFrameworkPackages.useQuery;
7447
+ /** Generated alias around `trpc.addons.listCapabilityProviders.useQuery`. */
7448
+ var useAddonsListCapabilityProviders = trpc.addons.listCapabilityProviders.useQuery;
7449
+ /** Generated alias around `trpc.addons.setCapabilityProviderEnabled.useMutation`. */
7450
+ var useAddonsSetCapabilityProviderEnabled = trpc.addons.setCapabilityProviderEnabled.useMutation;
7451
+ /** Generated alias around `trpc.addons.getVersions.useQuery`. */
7452
+ var useAddonsGetVersions = trpc.addons.getVersions.useQuery;
7453
+ /** Generated alias around `trpc.addons.restartAddon.useMutation`. */
7454
+ var useAddonsRestartAddon = trpc.addons.restartAddon.useMutation;
7455
+ /** Generated alias around `trpc.addons.retryLoad.useMutation`. */
7456
+ var useAddonsRetryLoad = trpc.addons.retryLoad.useMutation;
7457
+ /** Generated alias around `trpc.addons.getAutoUpdateSettings.useQuery`. */
7458
+ var useAddonsGetAutoUpdateSettings = trpc.addons.getAutoUpdateSettings.useQuery;
7459
+ /** Generated alias around `trpc.addons.setAutoUpdateSettings.useMutation`. */
7460
+ var useAddonsSetAutoUpdateSettings = trpc.addons.setAutoUpdateSettings.useMutation;
7461
+ /** Generated alias around `trpc.addons.getAddonAutoUpdate.useQuery`. */
7462
+ var useAddonsGetAddonAutoUpdate = trpc.addons.getAddonAutoUpdate.useQuery;
7463
+ /** Generated alias around `trpc.addons.setAddonAutoUpdate.useMutation`. */
7464
+ var useAddonsSetAddonAutoUpdate = trpc.addons.setAddonAutoUpdate.useMutation;
7465
+ /** Generated alias around `trpc.addons.applyAutoUpdateToAll.useMutation`. */
7466
+ var useAddonsApplyAutoUpdateToAll = trpc.addons.applyAutoUpdateToAll.useMutation;
7467
+ /** Generated alias around `trpc.addons.custom.useMutation`. */
7468
+ var useAddonsCustom = trpc.addons.custom.useMutation;
7469
+ /** Generated alias around `trpc.addons.startJob.useMutation`. */
7470
+ var useAddonsStartJob = trpc.addons.startJob.useMutation;
7471
+ /** Generated alias around `trpc.addons.getJob.useQuery`. */
7472
+ var useAddonsGetJob = trpc.addons.getJob.useQuery;
7473
+ /** Generated alias around `trpc.addons.listJobs.useQuery`. */
7474
+ var useAddonsListJobs = trpc.addons.listJobs.useQuery;
7475
+ /** Generated alias around `trpc.addons.cancelJob.useMutation`. */
7476
+ var useAddonsCancelJob = trpc.addons.cancelJob.useMutation;
7477
+ /** Generated alias around `trpc.addons.onAddonLogs.useSubscription`. */
7478
+ var useAddonsOnAddonLogs = trpc.addons.onAddonLogs.useSubscription;
7479
+ /** Generated alias around `trpc.addonSettings.getGlobalSettings.useQuery`. */
7480
+ var useAddonSettingsGetGlobalSettings = trpc.addonSettings.getGlobalSettings.useQuery;
7481
+ /** Generated alias around `trpc.addonSettings.updateGlobalSettings.useMutation`. */
7482
+ var useAddonSettingsUpdateGlobalSettings = trpc.addonSettings.updateGlobalSettings.useMutation;
7483
+ /** Generated alias around `trpc.addonSettings.getIntegrationSettings.useQuery`. */
7484
+ var useAddonSettingsGetIntegrationSettings = trpc.addonSettings.getIntegrationSettings.useQuery;
7485
+ /** Generated alias around `trpc.addonSettings.getDeviceSettings.useQuery`. */
7486
+ var useAddonSettingsGetDeviceSettings = trpc.addonSettings.getDeviceSettings.useQuery;
7487
+ /** Generated alias around `trpc.addonSettings.updateDeviceSettings.useMutation`. */
7488
+ var useAddonSettingsUpdateDeviceSettings = trpc.addonSettings.updateDeviceSettings.useMutation;
7489
+ /** Generated alias around `trpc.addonWidgets.listWidgets.useQuery`. */
7490
+ var useAddonWidgetsListWidgets = trpc.addonWidgets.listWidgets.useQuery;
7491
+ /** Generated alias around `trpc.airQualitySensor.getStatus.useQuery`. */
7492
+ var useAirQualitySensorGetStatus = trpc.airQualitySensor.getStatus.useQuery;
7493
+ /** Generated alias around `trpc.alarmPanel.arm.useMutation`. */
7494
+ var useAlarmPanelArm = trpc.alarmPanel.arm.useMutation;
7495
+ /** Generated alias around `trpc.alarmPanel.disarm.useMutation`. */
7496
+ var useAlarmPanelDisarm = trpc.alarmPanel.disarm.useMutation;
7497
+ /** Generated alias around `trpc.alarmPanel.trigger.useMutation`. */
7498
+ var useAlarmPanelTrigger = trpc.alarmPanel.trigger.useMutation;
7499
+ /** Generated alias around `trpc.alarmPanel.getStatus.useQuery`. */
7500
+ var useAlarmPanelGetStatus = trpc.alarmPanel.getStatus.useQuery;
7501
+ /** Generated alias around `trpc.alerts.emit.useMutation`. */
7502
+ var useAlertsEmit = trpc.alerts.emit.useMutation;
7503
+ /** Generated alias around `trpc.alerts.update.useMutation`. */
7504
+ var useAlertsUpdate = trpc.alerts.update.useMutation;
7505
+ /** Generated alias around `trpc.alerts.list.useQuery`. */
7506
+ var useAlertsList = trpc.alerts.list.useQuery;
7507
+ /** Generated alias around `trpc.alerts.getUnreadCount.useQuery`. */
7508
+ var useAlertsGetUnreadCount = trpc.alerts.getUnreadCount.useQuery;
7509
+ /** Generated alias around `trpc.alerts.markRead.useMutation`. */
7510
+ var useAlertsMarkRead = trpc.alerts.markRead.useMutation;
7511
+ /** Generated alias around `trpc.alerts.markAllRead.useMutation`. */
7512
+ var useAlertsMarkAllRead = trpc.alerts.markAllRead.useMutation;
7513
+ /** Generated alias around `trpc.alerts.dismiss.useMutation`. */
7514
+ var useAlertsDismiss = trpc.alerts.dismiss.useMutation;
7515
+ /** Generated alias around `trpc.ambientLightSensor.getStatus.useQuery`. */
7516
+ var useAmbientLightSensorGetStatus = trpc.ambientLightSensor.getStatus.useQuery;
7517
+ /** Generated alias around `trpc.audioAnalysis.resolveDeviceSettings.useQuery`. */
7518
+ var useAudioAnalysisResolveDeviceSettings = trpc.audioAnalysis.resolveDeviceSettings.useQuery;
7519
+ /** Generated alias around `trpc.audioAnalysis.getDeviceSettingsContribution.useQuery`. */
7520
+ var useAudioAnalysisGetDeviceSettingsContribution = trpc.audioAnalysis.getDeviceSettingsContribution.useQuery;
7521
+ /** Generated alias around `trpc.audioAnalysis.getDeviceLiveContribution.useQuery`. */
6579
7522
  var useAudioAnalysisGetDeviceLiveContribution = trpc.audioAnalysis.getDeviceLiveContribution.useQuery;
6580
7523
  /** Generated alias around `trpc.audioAnalysis.applyDeviceSettingsPatch.useMutation`. */
6581
7524
  var useAudioAnalysisApplyDeviceSettingsPatch = trpc.audioAnalysis.applyDeviceSettingsPatch.useMutation;
@@ -7243,6 +8186,24 @@ var useIntercomPushTalkAudio = trpc.intercom.pushTalkAudio.useMutation;
7243
8186
  var useIntercomEndTalkSession = trpc.intercom.endTalkSession.useMutation;
7244
8187
  /** Generated alias around `trpc.intercom.getStatus.useQuery`. */
7245
8188
  var useIntercomGetStatus = trpc.intercom.getStatus.useQuery;
8189
+ /** Generated alias around `trpc.keychainAdmin.status.useQuery`. */
8190
+ var useKeychainAdminStatus = trpc.keychainAdmin.status.useQuery;
8191
+ /** Generated alias around `trpc.keychainAdmin.listSecrets.useQuery`. */
8192
+ var useKeychainAdminListSecrets = trpc.keychainAdmin.listSecrets.useQuery;
8193
+ /** Generated alias around `trpc.keychainAdmin.listAudit.useQuery`. */
8194
+ var useKeychainAdminListAudit = trpc.keychainAdmin.listAudit.useQuery;
8195
+ /** Generated alias around `trpc.keychainAdmin.reveal.useMutation`. */
8196
+ var useKeychainAdminReveal = trpc.keychainAdmin.reveal.useMutation;
8197
+ /** Generated alias around `trpc.keychainAdmin.deleteOrphans.useMutation`. */
8198
+ var useKeychainAdminDeleteOrphans = trpc.keychainAdmin.deleteOrphans.useMutation;
8199
+ /** Generated alias around `trpc.keychainAdmin.rotateMasterKey.useMutation`. */
8200
+ var useKeychainAdminRotateMasterKey = trpc.keychainAdmin.rotateMasterKey.useMutation;
8201
+ /** Generated alias around `trpc.keychainAdmin.purgeRetiredKeys.useMutation`. */
8202
+ var useKeychainAdminPurgeRetiredKeys = trpc.keychainAdmin.purgeRetiredKeys.useMutation;
8203
+ /** Generated alias around `trpc.keychainAdmin.downloadRecoveryKey.useMutation`. */
8204
+ var useKeychainAdminDownloadRecoveryKey = trpc.keychainAdmin.downloadRecoveryKey.useMutation;
8205
+ /** Generated alias around `trpc.keychainAdmin.resetKeychain.useMutation`. */
8206
+ var useKeychainAdminResetKeychain = trpc.keychainAdmin.resetKeychain.useMutation;
7246
8207
  /** Generated alias around `trpc.lawnMowerControl.startMowing.useMutation`. */
7247
8208
  var useLawnMowerControlStartMowing = trpc.lawnMowerControl.startMowing.useMutation;
7248
8209
  /** Generated alias around `trpc.lawnMowerControl.pause.useMutation`. */
@@ -7779,6 +8740,30 @@ var usePipelineAnalyticsCompleteRetrainTrack = trpc.pipelineAnalytics.completeRe
7779
8740
  var usePipelineAnalyticsRestageRetrainTrack = trpc.pipelineAnalytics.restageRetrainTrack.useMutation;
7780
8741
  /** Generated alias around `trpc.pipelineAnalytics.getRetrainExportUrl.useQuery`. */
7781
8742
  var usePipelineAnalyticsGetRetrainExportUrl = trpc.pipelineAnalytics.getRetrainExportUrl.useQuery;
8743
+ /** Generated alias around `trpc.pipelineAnalytics.setRetrainFrameComplete.useMutation`. */
8744
+ var usePipelineAnalyticsSetRetrainFrameComplete = trpc.pipelineAnalytics.setRetrainFrameComplete.useMutation;
8745
+ /** Generated alias around `trpc.pipelineAnalytics.writeRetrainArchive.useMutation`. */
8746
+ var usePipelineAnalyticsWriteRetrainArchive = trpc.pipelineAnalytics.writeRetrainArchive.useMutation;
8747
+ /** Generated alias around `trpc.pipelineAnalytics.listRetrainAssistProfiles.useQuery`. */
8748
+ var usePipelineAnalyticsListRetrainAssistProfiles = trpc.pipelineAnalytics.listRetrainAssistProfiles.useQuery;
8749
+ /** Generated alias around `trpc.pipelineAnalytics.proposeRetrainAnnotationsWithLlm.useMutation`. */
8750
+ var usePipelineAnalyticsProposeRetrainAnnotationsWithLlm = trpc.pipelineAnalytics.proposeRetrainAnnotationsWithLlm.useMutation;
8751
+ /** Generated alias around `trpc.pipelineAnalytics.createRetrainCollection.useMutation`. */
8752
+ var usePipelineAnalyticsCreateRetrainCollection = trpc.pipelineAnalytics.createRetrainCollection.useMutation;
8753
+ /** Generated alias around `trpc.pipelineAnalytics.listRetrainCollections.useQuery`. */
8754
+ var usePipelineAnalyticsListRetrainCollections = trpc.pipelineAnalytics.listRetrainCollections.useQuery;
8755
+ /** Generated alias around `trpc.pipelineAnalytics.renameRetrainCollection.useMutation`. */
8756
+ var usePipelineAnalyticsRenameRetrainCollection = trpc.pipelineAnalytics.renameRetrainCollection.useMutation;
8757
+ /** Generated alias around `trpc.pipelineAnalytics.deleteRetrainCollection.useMutation`. */
8758
+ var usePipelineAnalyticsDeleteRetrainCollection = trpc.pipelineAnalytics.deleteRetrainCollection.useMutation;
8759
+ /** Generated alias around `trpc.pipelineAnalytics.addRetrainFrameFromRecording.useMutation`. */
8760
+ var usePipelineAnalyticsAddRetrainFrameFromRecording = trpc.pipelineAnalytics.addRetrainFrameFromRecording.useMutation;
8761
+ /** Generated alias around `trpc.pipelineAnalytics.addRetrainFramesToCollection.useMutation`. */
8762
+ var usePipelineAnalyticsAddRetrainFramesToCollection = trpc.pipelineAnalytics.addRetrainFramesToCollection.useMutation;
8763
+ /** Generated alias around `trpc.pipelineAnalytics.removeRetrainFrameFromCollection.useMutation`. */
8764
+ var usePipelineAnalyticsRemoveRetrainFrameFromCollection = trpc.pipelineAnalytics.removeRetrainFrameFromCollection.useMutation;
8765
+ /** Generated alias around `trpc.pipelineAnalytics.listRetrainCollectionFrames.useQuery`. */
8766
+ var usePipelineAnalyticsListRetrainCollectionFrames = trpc.pipelineAnalytics.listRetrainCollectionFrames.useQuery;
7782
8767
  /** Generated alias around `trpc.pipelineAnalytics.getEventMedia.useQuery`. */
7783
8768
  var usePipelineAnalyticsGetEventMedia = trpc.pipelineAnalytics.getEventMedia.useQuery;
7784
8769
  /** Generated alias around `trpc.pipelineAnalytics.getTrackMedia.useQuery`. */
@@ -7951,6 +8936,20 @@ var usePipelineOrchestratorSuggestShares = trpc.pipelineOrchestrator.suggestShar
7951
8936
  var usePipelineOrchestratorGetEffectiveShares = trpc.pipelineOrchestrator.getEffectiveShares.useQuery;
7952
8937
  /** Generated alias around `trpc.pipelineOrchestrator.setShareSource.useMutation`. */
7953
8938
  var usePipelineOrchestratorSetShareSource = trpc.pipelineOrchestrator.setShareSource.useMutation;
8939
+ /** Generated alias around `trpc.pipelineOrchestrator.getNodeBenchmarkPreflight.useQuery`. */
8940
+ var usePipelineOrchestratorGetNodeBenchmarkPreflight = trpc.pipelineOrchestrator.getNodeBenchmarkPreflight.useQuery;
8941
+ /** Generated alias around `trpc.pipelineOrchestrator.startNodeBenchmark.useMutation`. */
8942
+ var usePipelineOrchestratorStartNodeBenchmark = trpc.pipelineOrchestrator.startNodeBenchmark.useMutation;
8943
+ /** Generated alias around `trpc.pipelineOrchestrator.getNodeBenchmarkStatus.useQuery`. */
8944
+ var usePipelineOrchestratorGetNodeBenchmarkStatus = trpc.pipelineOrchestrator.getNodeBenchmarkStatus.useQuery;
8945
+ /** Generated alias around `trpc.pipelineOrchestrator.abortNodeBenchmark.useMutation`. */
8946
+ var usePipelineOrchestratorAbortNodeBenchmark = trpc.pipelineOrchestrator.abortNodeBenchmark.useMutation;
8947
+ /** Generated alias around `trpc.pipelineOrchestrator.getNodeBenchmarkCandidates.useQuery`. */
8948
+ var usePipelineOrchestratorGetNodeBenchmarkCandidates = trpc.pipelineOrchestrator.getNodeBenchmarkCandidates.useQuery;
8949
+ /** Generated alias around `trpc.pipelineOrchestrator.listNodeBenchmarkResults.useQuery`. */
8950
+ var usePipelineOrchestratorListNodeBenchmarkResults = trpc.pipelineOrchestrator.listNodeBenchmarkResults.useQuery;
8951
+ /** Generated alias around `trpc.pipelineOrchestrator.deleteNodeBenchmarkResult.useMutation`. */
8952
+ var usePipelineOrchestratorDeleteNodeBenchmarkResult = trpc.pipelineOrchestrator.deleteNodeBenchmarkResult.useMutation;
7954
8953
  /** Generated alias around `trpc.pipelineOrchestrator.getCameraSettings.useQuery`. */
7955
8954
  var usePipelineOrchestratorGetCameraSettings = trpc.pipelineOrchestrator.getCameraSettings.useQuery;
7956
8955
  /** Generated alias around `trpc.pipelineOrchestrator.setCameraStepToggle.useMutation`. */
@@ -8015,6 +9014,12 @@ var usePipelineRunnerParkTrackFrame = trpc.pipelineRunner.parkTrackFrame.useMuta
8015
9014
  var usePipelineRunnerGetParkedTrackFrame = trpc.pipelineRunner.getParkedTrackFrame.useQuery;
8016
9015
  /** Generated alias around `trpc.pipelineRunner.releaseParkedTrackFrames.useMutation`. */
8017
9016
  var usePipelineRunnerReleaseParkedTrackFrames = trpc.pipelineRunner.releaseParkedTrackFrames.useMutation;
9017
+ /** Generated alias around `trpc.pipelineRunner.startNodeBench.useMutation`. */
9018
+ var usePipelineRunnerStartNodeBench = trpc.pipelineRunner.startNodeBench.useMutation;
9019
+ /** Generated alias around `trpc.pipelineRunner.getNodeBenchStatus.useQuery`. */
9020
+ var usePipelineRunnerGetNodeBenchStatus = trpc.pipelineRunner.getNodeBenchStatus.useQuery;
9021
+ /** Generated alias around `trpc.pipelineRunner.abortNodeBench.useMutation`. */
9022
+ var usePipelineRunnerAbortNodeBench = trpc.pipelineRunner.abortNodeBench.useMutation;
8018
9023
  /** Generated alias around `trpc.plateGallery.listPlates.useQuery`. */
8019
9024
  var usePlateGalleryListPlates = trpc.plateGallery.listPlates.useQuery;
8020
9025
  /** Generated alias around `trpc.plateGallery.getPlateByTrack.useQuery`. */
@@ -8567,6 +9572,14 @@ var useUserManagementOauthExchangeCode = trpc.userManagement.oauthExchangeCode.u
8567
9572
  var useUserManagementOauthRefresh = trpc.userManagement.oauthRefresh.useMutation;
8568
9573
  /** Generated alias around `trpc.userManagement.oauthVerifyAccessToken.useQuery`. */
8569
9574
  var useUserManagementOauthVerifyAccessToken = trpc.userManagement.oauthVerifyAccessToken.useQuery;
9575
+ /** Generated alias around `trpc.userManagement.listExternalIdentities.useQuery`. */
9576
+ var useUserManagementListExternalIdentities = trpc.userManagement.listExternalIdentities.useQuery;
9577
+ /** Generated alias around `trpc.userManagement.findUserByExternalIdentity.useQuery`. */
9578
+ var useUserManagementFindUserByExternalIdentity = trpc.userManagement.findUserByExternalIdentity.useQuery;
9579
+ /** Generated alias around `trpc.userManagement.linkExternalIdentity.useMutation`. */
9580
+ var useUserManagementLinkExternalIdentity = trpc.userManagement.linkExternalIdentity.useMutation;
9581
+ /** Generated alias around `trpc.userManagement.unlinkExternalIdentity.useMutation`. */
9582
+ var useUserManagementUnlinkExternalIdentity = trpc.userManagement.unlinkExternalIdentity.useMutation;
8570
9583
  /** Generated alias around `trpc.userManagement.listOauthSessions.useQuery`. */
8571
9584
  var useUserManagementListOauthSessions = trpc.userManagement.listOauthSessions.useQuery;
8572
9585
  /** Generated alias around `trpc.userManagement.revokeOauthSession.useMutation`. */
@@ -20852,7 +21865,7 @@ function formatDurationMs(ms) {
20852
21865
  * firmware inventing a refusal reaches the operator rather than becoming a
20853
21866
  * blank tile.
20854
21867
  */
20855
- var REFUSAL_TEXT = {
21868
+ var REFUSAL_TEXT$1 = {
20856
21869
  sleeping: () => "The camera is asleep — a still would cost a wake",
20857
21870
  "camera-refused": (reason) => `The camera refused — ${reason}`,
20858
21871
  "no-keyframe": () => "No decodable frame inside this window",
@@ -20864,7 +21877,7 @@ var REFUSAL_TEXT = {
20864
21877
  "uid-missing": () => "The camera’s id is not known yet"
20865
21878
  };
20866
21879
  function wordsFor(code, reason) {
20867
- const known = code === void 0 ? void 0 : REFUSAL_TEXT[code];
21880
+ const known = code === void 0 ? void 0 : REFUSAL_TEXT$1[code];
20868
21881
  return known === void 0 ? reason : known(reason);
20869
21882
  }
20870
21883
  function refusal(reason, code) {
@@ -24447,6 +25460,426 @@ function useScrubController(sink, isPlayerLive = () => false) {
24447
25460
  };
24448
25461
  }
24449
25462
  //#endregion
25463
+ //#region src/primitives/dialog.tsx
25464
+ var DialogContext = createContext(null);
25465
+ function useDialogContext() {
25466
+ const ctx = useContext(DialogContext);
25467
+ if (!ctx) throw new Error("Dialog compound components must be used within <Dialog>");
25468
+ return ctx;
25469
+ }
25470
+ function Dialog({ children, open: controlledOpen, onOpenChange }) {
25471
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(false);
25472
+ const open = controlledOpen ?? uncontrolledOpen;
25473
+ const contentId = useId();
25474
+ const setOpen = useCallback((next) => {
25475
+ onOpenChange?.(next);
25476
+ if (controlledOpen === void 0) setUncontrolledOpen(next);
25477
+ }, [controlledOpen, onOpenChange]);
25478
+ return /* @__PURE__ */ jsx(DialogContext.Provider, {
25479
+ value: {
25480
+ open,
25481
+ setOpen,
25482
+ contentId
25483
+ },
25484
+ children
25485
+ });
25486
+ }
25487
+ function DialogTrigger({ children, ...props }) {
25488
+ const { setOpen } = useDialogContext();
25489
+ return /* @__PURE__ */ jsx("button", {
25490
+ type: "button",
25491
+ onClick: () => setOpen(true),
25492
+ ...props,
25493
+ children
25494
+ });
25495
+ }
25496
+ /**
25497
+ * A native <dialog> opened with showModal() is promoted to the top layer, so
25498
+ * ancestor transform/overflow can't clip it — but it relies on the UA
25499
+ * stylesheet's `dialog { margin: auto }` for modal centering. Tailwind's
25500
+ * preflight resets `margin: 0` on every element, which defeats that centering
25501
+ * and pins the modal to the top-left. We therefore center deterministically:
25502
+ * a top-layer dialog with `position: fixed` is viewport-relative, and a
25503
+ * self-transform is fine, so `fixed left-1/2 top-1/2 -translate-x-1/2
25504
+ * -translate-y-1/2 m-0` always centers regardless of any margin reset. Width
25505
+ * is capped to the viewport (`w-[calc(100vw-2rem)]`) rather than a bare
25506
+ * `w-full` (which would overflow/clip a centered dialog); the per-variant
25507
+ * `max-w-*` keeps small dialogs small while the cap prevents overflow.
25508
+ *
25509
+ * Height is capped against the SAFE viewport, not the raw one. A top-layer
25510
+ * `fixed` dialog is viewport-relative, and under `viewport-fit=cover` the
25511
+ * viewport extends behind the iOS notch/Dynamic Island and home indicator —
25512
+ * so a dialog tall enough to hit its `max-h` put its header under the clock.
25513
+ * Because the box is centred on the viewport (not on the safe area), the cap
25514
+ * has to subtract TWICE the LARGER of the two insets for the box to clear
25515
+ * both edges; using each inset once would still overflow the taller side
25516
+ * whenever they are asymmetric (they are: ~59px top vs ~34px bottom). Every
25517
+ * term collapses to the previous `100dvh - 2rem` where the insets are 0.
25518
+ */
25519
+ var contentVariants = cva("fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 m-0 w-[calc(100vw-2rem)] bg-background-elevated text-foreground border border-border rounded-lg p-4 backdrop:bg-black/50 backdrop:backdrop-blur-sm max-h-[calc(100dvh-2rem-2*max(env(safe-area-inset-top),env(safe-area-inset-bottom)))] [&:not([open])]:hidden", {
25520
+ variants: {
25521
+ width: {
25522
+ sm: "max-w-sm",
25523
+ md: "max-w-md",
25524
+ lg: "max-w-lg"
25525
+ },
25526
+ /**
25527
+ * WHICH box scrolls when the content is taller than the height cap.
25528
+ *
25529
+ * `content` (default) scrolls the whole dialog: the title, any tab strip
25530
+ * and the Save/Cancel row travel with the fields. That is fine for a
25531
+ * short form and wrong for a long one — the operator scrolls to the
25532
+ * bottom of a rule editor and the buttons are somewhere above him, the
25533
+ * tab strip is gone, and the only way back to either is to scroll the
25534
+ * form he was reading.
25535
+ *
25536
+ * `body` makes the dialog a flex column that does not scroll at all and
25537
+ * hands the scrolling to `<DialogBody>`. The chrome around the body —
25538
+ * header, tab strip, footer — stays put at every height. The dialog
25539
+ * keeps its own padding either way, so `sheet-on-mobile`'s
25540
+ * safe-area bottom padding still applies to the footer.
25541
+ *
25542
+ * `body` also takes a DEFINITE height below `md` — see the
25543
+ * `scroll: 'body'` compound variants. That is not a style choice; the
25544
+ * flex chain does not resolve without it on iOS Safari.
25545
+ */
25546
+ scroll: {
25547
+ content: "overflow-y-auto",
25548
+ body: "flex flex-col overflow-hidden"
25549
+ },
25550
+ placement: {
25551
+ center: "",
25552
+ "sheet-on-mobile": "bottom-0 left-0 right-0 top-auto w-full max-w-full translate-x-0 translate-y-0 rounded-b-none max-h-[92dvh] pb-[calc(1rem+env(safe-area-inset-bottom))] md:left-1/2 md:top-1/2 md:-translate-x-1/2 md:-translate-y-1/2 md:rounded-lg md:bottom-auto md:pb-4 md:max-h-[calc(100dvh-2rem-2*max(env(safe-area-inset-top),env(safe-area-inset-bottom)))]"
25553
+ }
25554
+ },
25555
+ compoundVariants: [
25556
+ {
25557
+ placement: "sheet-on-mobile",
25558
+ width: "sm",
25559
+ class: "md:w-[calc(100vw-2rem)] md:max-w-sm"
25560
+ },
25561
+ {
25562
+ placement: "sheet-on-mobile",
25563
+ width: "md",
25564
+ class: "md:w-[calc(100vw-2rem)] md:max-w-md"
25565
+ },
25566
+ {
25567
+ placement: "sheet-on-mobile",
25568
+ width: "lg",
25569
+ class: "md:w-[calc(100vw-2rem)] md:max-w-lg"
25570
+ },
25571
+ {
25572
+ scroll: "body",
25573
+ placement: "sheet-on-mobile",
25574
+ class: "h-[92dvh] md:h-auto"
25575
+ },
25576
+ {
25577
+ scroll: "body",
25578
+ placement: "center",
25579
+ class: "h-[calc(100dvh-2rem-2*max(env(safe-area-inset-top),env(safe-area-inset-bottom)))] md:h-auto"
25580
+ }
25581
+ ],
25582
+ defaultVariants: {
25583
+ width: "md",
25584
+ placement: "center",
25585
+ scroll: "content"
25586
+ }
25587
+ });
25588
+ var DialogContent = forwardRef(({ className, width, placement, scroll, children, ...props }, ref) => {
25589
+ const { open, setOpen, contentId } = useDialogContext();
25590
+ const innerRef = useRef(null);
25591
+ const dialogRef = ref ?? innerRef;
25592
+ useEffect(() => {
25593
+ const el = dialogRef.current;
25594
+ if (!el) return;
25595
+ if (open && !el.open) el.showModal();
25596
+ if (!open && el.open) el.close();
25597
+ }, [open, dialogRef]);
25598
+ return /* @__PURE__ */ jsx("dialog", {
25599
+ ref: dialogRef,
25600
+ id: contentId,
25601
+ className: cn(contentVariants({
25602
+ width,
25603
+ placement,
25604
+ scroll
25605
+ }), className),
25606
+ onClose: () => setOpen(false),
25607
+ ...props,
25608
+ children
25609
+ });
25610
+ });
25611
+ DialogContent.displayName = "DialogContent";
25612
+ /** `shrink-0` is inert outside a flex column and load-bearing inside one:
25613
+ * under `scroll="body"` the header must keep its height while the body gives
25614
+ * ground, or a long form squeezes its own title to nothing. */
25615
+ function DialogHeader({ className, ...props }) {
25616
+ return /* @__PURE__ */ jsx("div", {
25617
+ className: cn("flex flex-col gap-1 mb-3 shrink-0", className),
25618
+ ...props
25619
+ });
25620
+ }
25621
+ /**
25622
+ * The scrolling middle of a `scroll="body"` dialog.
25623
+ *
25624
+ * `min-h-0` is what makes it work: a flex item's default `min-height: auto`
25625
+ * refuses to shrink below its content, so without it the body pushes the
25626
+ * footer out of the dialog instead of scrolling — the exact failure this
25627
+ * variant exists to remove. Anything sticky INSIDE a dialog body pins against
25628
+ * this element, since it is the scroll container.
25629
+ *
25630
+ * It only bounds a scroll area if every ancestor up to the dialog has a
25631
+ * resolved height. `DialogContent` supplies the top of that chain (see the
25632
+ * `scroll: 'body'` compound variants); an intermediate wrapper between them —
25633
+ * a `<Tabs>` that owns the tab strip, say — must carry `flex-1 min-h-0` too,
25634
+ * or it collapses and takes the body with it.
25635
+ */
25636
+ function DialogBody({ className, ...props }) {
25637
+ return /* @__PURE__ */ jsx("div", {
25638
+ className: cn("flex-1 min-h-0 overflow-y-auto", className),
25639
+ ...props
25640
+ });
25641
+ }
25642
+ function DialogFooter({ className, ...props }) {
25643
+ return /* @__PURE__ */ jsx("div", {
25644
+ className: cn("flex justify-end gap-2 mt-4 shrink-0", className),
25645
+ ...props
25646
+ });
25647
+ }
25648
+ function DialogTitle({ className, ...props }) {
25649
+ return /* @__PURE__ */ jsx("h2", {
25650
+ className: cn("text-sm font-semibold text-foreground", className),
25651
+ ...props
25652
+ });
25653
+ }
25654
+ function DialogDescription({ className, ...props }) {
25655
+ return /* @__PURE__ */ jsx("p", {
25656
+ className: cn("text-xs text-foreground-muted", className),
25657
+ ...props
25658
+ });
25659
+ }
25660
+ //#endregion
25661
+ //#region src/primitives/input.tsx
25662
+ /**
25663
+ * `disabled:opacity-50 disabled:cursor-not-allowed` for the same reason as
25664
+ * `Select` — an explicit `bg-surface` / `text-foreground` overrides the greying
25665
+ * a browser would otherwise apply, leaving an inert field indistinguishable
25666
+ * from a live one. See `disabled-affordance.test.tsx`.
25667
+ */
25668
+ var inputVariants = cva("h-7 w-full px-2.5 text-xs bg-surface border rounded-md text-foreground placeholder:text-foreground-subtle focus:outline-none focus:ring-1 focus:ring-primary transition-colors disabled:opacity-50 disabled:cursor-not-allowed", {
25669
+ variants: { state: {
25670
+ default: "border-border",
25671
+ error: "border-danger"
25672
+ } },
25673
+ defaultVariants: { state: "default" }
25674
+ });
25675
+ var Input = forwardRef(({ className, state, leftSlot, rightSlot, ...props }, ref) => {
25676
+ if (leftSlot || rightSlot) return /* @__PURE__ */ jsxs("div", {
25677
+ className: "relative flex items-center w-full",
25678
+ children: [
25679
+ leftSlot && /* @__PURE__ */ jsx("div", {
25680
+ className: "absolute left-2.5 flex items-center pointer-events-none",
25681
+ children: leftSlot
25682
+ }),
25683
+ /* @__PURE__ */ jsx("input", {
25684
+ ref,
25685
+ className: cn(inputVariants({ state }), leftSlot ? "pl-7" : "", rightSlot ? "pr-7" : "", className),
25686
+ ...props
25687
+ }),
25688
+ rightSlot && /* @__PURE__ */ jsx("div", {
25689
+ className: "absolute right-2.5 flex items-center pointer-events-none",
25690
+ children: rightSlot
25691
+ })
25692
+ ]
25693
+ });
25694
+ return /* @__PURE__ */ jsx("input", {
25695
+ ref,
25696
+ className: cn(inputVariants({ state }), className),
25697
+ ...props
25698
+ });
25699
+ });
25700
+ Input.displayName = "Input";
25701
+ //#endregion
25702
+ //#region src/composites/training/training-collection-picker.tsx
25703
+ /**
25704
+ * <TrainingCollectionPicker> — choose a named training collection, or create
25705
+ * one in place.
25706
+ *
25707
+ * A collection is a named set over the retrain corpus ("packages porch
25708
+ * night"). The picker lists them (newest first, with their frame counts) and
25709
+ * offers a name field for a new one; the hub refuses an empty or duplicate
25710
+ * name, and the refusal is shown here rather than swallowed.
25711
+ */
25712
+ var CREATE_REFUSAL = {
25713
+ "empty-name": "Give the collection a name.",
25714
+ "duplicate-name": "A collection with that name already exists."
25715
+ };
25716
+ function TrainingCollectionPicker({ value, onChange }) {
25717
+ const collections = usePipelineAnalyticsListRetrainCollections({});
25718
+ const create = usePipelineAnalyticsCreateRetrainCollection();
25719
+ const [name, setName] = useState("");
25720
+ const [error, setError] = useState(null);
25721
+ const list = collections.data ?? [];
25722
+ const onCreate = async () => {
25723
+ setError(null);
25724
+ const result = await create.mutateAsync({ name });
25725
+ if (result.kind === "refused") {
25726
+ setError(CREATE_REFUSAL[result.reason]);
25727
+ return;
25728
+ }
25729
+ setName("");
25730
+ await collections.refetch();
25731
+ onChange(result.collection.id);
25732
+ };
25733
+ return /* @__PURE__ */ jsxs("div", {
25734
+ className: "space-y-2",
25735
+ "data-testid": "training-collection-picker",
25736
+ children: [
25737
+ list.length > 0 ? /* @__PURE__ */ jsx(Select, {
25738
+ "aria-label": "Collection",
25739
+ value: value ?? "",
25740
+ options: [{
25741
+ value: "",
25742
+ label: "Choose a collection…"
25743
+ }, ...list.map((c) => ({
25744
+ value: c.id,
25745
+ label: `${c.name} (${String(c.frameCount)})`
25746
+ }))],
25747
+ onChange: (event) => {
25748
+ if (event.target.value !== "") onChange(event.target.value);
25749
+ }
25750
+ }) : /* @__PURE__ */ jsx("p", {
25751
+ className: "text-xs text-foreground-subtle",
25752
+ children: collections.isLoading ? "Reading the collections…" : "No collection yet — create one."
25753
+ }),
25754
+ /* @__PURE__ */ jsxs("div", {
25755
+ className: "flex items-center gap-2",
25756
+ children: [/* @__PURE__ */ jsx(Input, {
25757
+ className: "flex-1",
25758
+ "aria-label": "New collection name",
25759
+ placeholder: "New collection (e.g. missed at night)",
25760
+ value: name,
25761
+ onChange: (event) => {
25762
+ setName(event.target.value);
25763
+ }
25764
+ }), /* @__PURE__ */ jsxs(Button, {
25765
+ type: "button",
25766
+ variant: "secondary",
25767
+ size: "sm",
25768
+ disabled: create.isPending || name.trim() === "",
25769
+ onClick: () => void onCreate(),
25770
+ children: [/* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" }), "Create"]
25771
+ })]
25772
+ }),
25773
+ error !== null && /* @__PURE__ */ jsx("p", {
25774
+ className: "text-xs text-danger",
25775
+ children: error
25776
+ })
25777
+ ]
25778
+ });
25779
+ }
25780
+ //#endregion
25781
+ //#region src/composites/training/training-refusal-text.ts
25782
+ var REFUSAL_TEXT = {
25783
+ "unknown-collection": "That collection no longer exists — pick another one.",
25784
+ "still-unavailable": "No frame could be taken from the recording at that moment.",
25785
+ "still-busy": "The recorder is busy decoding other frames. Try again in a moment.",
25786
+ "unreadable-image": "The recorder returned something that is not a picture.",
25787
+ "write-failed": "The frame could not be stored. Check the artifacts storage."
25788
+ };
25789
+ /** One sentence for a refusal, with the hub's own detail appended. */
25790
+ function trainingRefusalText(reason, detail) {
25791
+ const base = REFUSAL_TEXT[reason];
25792
+ return detail !== void 0 && detail !== "" ? `${base} (${detail})` : base;
25793
+ }
25794
+ //#endregion
25795
+ //#region src/composites/training/send-frame-to-training.tsx
25796
+ /**
25797
+ * <SendFrameToTrainingButton> — "Send frame to training" from a recording
25798
+ * playhead.
25799
+ *
25800
+ * The frame a detector most needs to learn from is often one where it saw
25801
+ * NOTHING — no event, no track, nothing to mark. From the recording, the
25802
+ * operator stops on that moment and sends it: the hub re-decodes the native
25803
+ * frame at the playhead instant and adds it to a named training collection,
25804
+ * where the Retrain page annotates it like any other frame.
25805
+ *
25806
+ * Draws NOTHING for a session that cannot curate training data: the
25807
+ * collection list is admin-only, and a button that 403s is worse than no
25808
+ * button. It is disabled, with the reason, while there is no playhead.
25809
+ */
25810
+ function SendFrameToTrainingButton({ deviceId, epochMs }) {
25811
+ const collections = usePipelineAnalyticsListRetrainCollections({}, { retry: false });
25812
+ const send = usePipelineAnalyticsAddRetrainFrameFromRecording();
25813
+ const [open, setOpen] = useState(false);
25814
+ const [collectionId, setCollectionId] = useState(null);
25815
+ const [instant, setInstant] = useState(null);
25816
+ const [outcome, setOutcome] = useState(null);
25817
+ if (collections.isError) return null;
25818
+ const onSend = async () => {
25819
+ if (collectionId === null || instant === null) return;
25820
+ const result = await send.mutateAsync({
25821
+ collectionId,
25822
+ deviceId,
25823
+ epochMs: instant
25824
+ });
25825
+ const name = collections.data?.find((c) => c.id === collectionId)?.name ?? "the collection";
25826
+ setOutcome(result.kind === "added" ? {
25827
+ kind: "added",
25828
+ reused: result.reused,
25829
+ collection: name
25830
+ } : {
25831
+ kind: "refused",
25832
+ text: trainingRefusalText(result.reason, result.detail)
25833
+ });
25834
+ if (result.kind === "added") await collections.refetch();
25835
+ };
25836
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Button, {
25837
+ type: "button",
25838
+ variant: "secondary",
25839
+ size: "sm",
25840
+ "data-testid": "send-frame-to-training",
25841
+ disabled: epochMs === null,
25842
+ title: epochMs === null ? "Play back a recording first — the frame at the playhead is the one sent" : "Add the frame at the playhead to a training collection",
25843
+ onClick: () => {
25844
+ setInstant(epochMs);
25845
+ setOutcome(null);
25846
+ setOpen(true);
25847
+ },
25848
+ children: [/* @__PURE__ */ jsx(GraduationCap, { className: "h-3.5 w-3.5" }), "Send frame to training"]
25849
+ }), /* @__PURE__ */ jsx(Dialog, {
25850
+ open,
25851
+ onOpenChange: (next) => !send.isPending && setOpen(next),
25852
+ children: /* @__PURE__ */ jsxs(DialogContent, { children: [
25853
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: "Send this frame to training" }), /* @__PURE__ */ jsx(DialogDescription, { children: instant !== null ? `The camera's frame at ${new Date(instant).toLocaleString()} is taken from the recording at full resolution and added to the collection. Annotate it on the Retrain page — including what the model missed.` : "" })] }),
25854
+ /* @__PURE__ */ jsx(TrainingCollectionPicker, {
25855
+ value: collectionId,
25856
+ onChange: setCollectionId
25857
+ }),
25858
+ outcome !== null && /* @__PURE__ */ jsx("p", {
25859
+ className: `mt-2 text-xs ${outcome.kind === "added" ? "text-success" : "text-danger"}`,
25860
+ role: "status",
25861
+ children: outcome.kind === "added" ? outcome.reused ? `That moment was already in the dataset; it is now in ${outcome.collection}.` : `Added to ${outcome.collection}.` : outcome.text
25862
+ }),
25863
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button, {
25864
+ type: "button",
25865
+ variant: "ghost",
25866
+ size: "sm",
25867
+ disabled: send.isPending,
25868
+ onClick: () => {
25869
+ setOpen(false);
25870
+ },
25871
+ children: outcome?.kind === "added" ? "Done" : "Cancel"
25872
+ }), /* @__PURE__ */ jsxs(Button, {
25873
+ type: "button",
25874
+ size: "sm",
25875
+ disabled: collectionId === null || send.isPending || outcome?.kind === "added",
25876
+ onClick: () => void onSend(),
25877
+ children: [send.isPending && /* @__PURE__ */ jsx(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }), "Send"]
25878
+ })] })
25879
+ ] })
25880
+ })] });
25881
+ }
25882
+ //#endregion
24450
25883
  //#region src/composites/cap-settings/CoverageTrack.tsx
24451
25884
  function CoverageTrack({ windowFrom, windowTo, spans, playheadMs, onSeek, onScrubStart, onScrubMove, onScrubEnd }) {
24452
25885
  const [hoverInfo, setHoverInfo] = useState(null);
@@ -25025,6 +26458,13 @@ function RecordingTimeline({ deviceId }) {
25025
26458
  dayBounds: computedDayBounds,
25026
26459
  onViewChange: handleViewChange
25027
26460
  }),
26461
+ /* @__PURE__ */ jsx("div", {
26462
+ className: "flex justify-end",
26463
+ children: /* @__PURE__ */ jsx(SendFrameToTrainingButton, {
26464
+ deviceId,
26465
+ epochMs: displayPlayheadMs
26466
+ })
26467
+ }),
25028
26468
  recorded.channelOpen ? /* @__PURE__ */ jsxs("div", {
25029
26469
  "data-testid": "frame-push-status-row",
25030
26470
  className: "flex flex-wrap items-center gap-2 text-[10px]",
@@ -26622,6 +28062,32 @@ function FieldWrapper({ label, description, required, span, children, translatio
26622
28062
  ]
26623
28063
  });
26624
28064
  }
28065
+ /**
28066
+ * A keychain-stored secret (D787) arrives as its VIEW — `{ $secret, set }` —
28067
+ * or `null`, never as a value; it renders as "set · Change · Clear · Reveal".
28068
+ * Decided ONCE, at mount, from what the form was hydrated with: once the
28069
+ * operator types, the value is a plain string and the field must stay the
28070
+ * same control. A form that was not hydrated from a keychain-aware store (a
28071
+ * create dialog seeding `''`) keeps the plain input.
28072
+ */
28073
+ function KeychainOr({ field, value, onChange, disabled, translationFn, multiline, children }) {
28074
+ const [keychain] = useState(() => isKeychainStoredField(field) && isKeychainFieldValue(value));
28075
+ if (!keychain) return /* @__PURE__ */ jsx(Fragment$1, { children });
28076
+ return /* @__PURE__ */ jsx(FieldWrapper, {
28077
+ label: "label" in field ? field.label : void 0,
28078
+ description: "description" in field ? field.description : void 0,
28079
+ required: "required" in field ? field.required : void 0,
28080
+ span: "span" in field ? field.span : void 0,
28081
+ translationFn,
28082
+ children: /* @__PURE__ */ jsx(SecretFieldControl, {
28083
+ field,
28084
+ value,
28085
+ onChange,
28086
+ disabled,
28087
+ multiline
28088
+ })
28089
+ });
28090
+ }
26625
28091
  function TextField({ field, value, onChange, disabled, translationFn }) {
26626
28092
  return /* @__PURE__ */ jsx(FieldWrapper, {
26627
28093
  label: field.label,
@@ -27142,6 +28608,23 @@ function ColorField({ field, value, onChange, disabled, translationFn }) {
27142
28608
  })]
27143
28609
  });
27144
28610
  }
28611
+ function IconField({ field, value, onChange, disabled, translationFn }) {
28612
+ const defaultKey = typeof field.default === "string" ? field.default : "";
28613
+ return /* @__PURE__ */ jsx(FieldWrapper, {
28614
+ label: field.label,
28615
+ description: field.description,
28616
+ required: field.required,
28617
+ span: field.span,
28618
+ translationFn,
28619
+ children: /* @__PURE__ */ jsx(ConfigIconFieldControl, {
28620
+ label: resolveLabel(field.label, translationFn) ?? field.label,
28621
+ value,
28622
+ defaultKey,
28623
+ disabled: disabled || field.disabled,
28624
+ onChange
28625
+ })
28626
+ });
28627
+ }
27145
28628
  function ProbeField({ field, value, onChange, disabled, translationFn, onTestField, onAction, externalProbe }) {
27146
28629
  const [localStatus, setLocalStatus] = useState("idle");
27147
28630
  const [localResult, setLocalResult] = useState(null);
@@ -27677,7 +29160,7 @@ function resolveActionParams(field, values) {
27677
29160
  const params = { ...field.paramsStatic };
27678
29161
  if (field.paramsFromForm) for (const [paramKey, formKey] of Object.entries(field.paramsFromForm)) {
27679
29162
  const v = values[formKey];
27680
- if (v !== void 0 && v !== null && v !== "") params[paramKey] = v;
29163
+ if (v !== void 0 && v !== null && v !== "" && !isSecretClear(v)) params[paramKey] = v;
27681
29164
  }
27682
29165
  return params;
27683
29166
  }
@@ -27776,8 +29259,16 @@ function AddonActionButtonField({ field, values, disabled, onAction }) {
27776
29259
  const [loading, setLoading] = useState(false);
27777
29260
  const [status, setStatus] = useState(null);
27778
29261
  const confirm = useConfirm();
29262
+ const prompt = usePrompt();
27779
29263
  const deviceId = useDeviceId();
27780
29264
  const handleClick = async () => {
29265
+ const asked = field.prompt ? await prompt({
29266
+ title: field.prompt.title ?? field.label,
29267
+ ...field.prompt.message !== void 0 ? { message: field.prompt.message } : {},
29268
+ ...field.prompt.placeholder !== void 0 ? { placeholder: field.prompt.placeholder } : {},
29269
+ confirmLabel: field.buttonLabel
29270
+ }) : null;
29271
+ if (field.prompt && asked === null) return;
27781
29272
  if (field.confirmMessage) {
27782
29273
  if (!await confirm({
27783
29274
  title: field.label,
@@ -27788,6 +29279,7 @@ function AddonActionButtonField({ field, values, disabled, onAction }) {
27788
29279
  }
27789
29280
  if (!onAction) return;
27790
29281
  const params = resolveActionParams(field, values);
29282
+ if (field.prompt && asked !== null) params[field.prompt.param] = asked;
27791
29283
  if (field.includeDeviceId === true && deviceId !== null && deviceId !== void 0) params["deviceId"] = deviceId;
27792
29284
  setLoading(true);
27793
29285
  setStatus(null);
@@ -28164,12 +29656,19 @@ function FormField({ field, values, onChange, disabled, translationFn, onTestFie
28164
29656
  const handleChange = (v) => onChange(field.key, v);
28165
29657
  const customRenderer = useCustomFieldRenderer(field.type);
28166
29658
  switch (field.type) {
28167
- case "text": return /* @__PURE__ */ jsx(TextField, {
29659
+ case "text": return /* @__PURE__ */ jsx(KeychainOr, {
28168
29660
  field,
28169
29661
  value,
28170
29662
  onChange: handleChange,
28171
29663
  disabled,
28172
- translationFn
29664
+ translationFn,
29665
+ children: /* @__PURE__ */ jsx(TextField, {
29666
+ field,
29667
+ value,
29668
+ onChange: handleChange,
29669
+ disabled,
29670
+ translationFn
29671
+ })
28173
29672
  });
28174
29673
  case "number": return /* @__PURE__ */ jsx(NumberField, {
28175
29674
  field,
@@ -28213,19 +29712,34 @@ function FormField({ field, values, onChange, disabled, translationFn, onTestFie
28213
29712
  disabled,
28214
29713
  translationFn
28215
29714
  });
28216
- case "password": return /* @__PURE__ */ jsx(PasswordField, {
29715
+ case "password": return /* @__PURE__ */ jsx(KeychainOr, {
28217
29716
  field,
28218
29717
  value,
28219
29718
  onChange: handleChange,
28220
29719
  disabled,
28221
- translationFn
29720
+ translationFn,
29721
+ children: /* @__PURE__ */ jsx(PasswordField, {
29722
+ field,
29723
+ value,
29724
+ onChange: handleChange,
29725
+ disabled,
29726
+ translationFn
29727
+ })
28222
29728
  });
28223
- case "textarea": return /* @__PURE__ */ jsx(TextAreaField, {
29729
+ case "textarea": return /* @__PURE__ */ jsx(KeychainOr, {
28224
29730
  field,
28225
29731
  value,
28226
29732
  onChange: handleChange,
28227
29733
  disabled,
28228
- translationFn
29734
+ translationFn,
29735
+ multiline: true,
29736
+ children: /* @__PURE__ */ jsx(TextAreaField, {
29737
+ field,
29738
+ value,
29739
+ onChange: handleChange,
29740
+ disabled,
29741
+ translationFn
29742
+ })
28229
29743
  });
28230
29744
  case "slider": return /* @__PURE__ */ jsx(SliderField, {
28231
29745
  field,
@@ -28248,6 +29762,13 @@ function FormField({ field, values, onChange, disabled, translationFn, onTestFie
28248
29762
  disabled,
28249
29763
  translationFn
28250
29764
  });
29765
+ case "icon": return /* @__PURE__ */ jsx(IconField, {
29766
+ field,
29767
+ value,
29768
+ onChange: handleChange,
29769
+ disabled,
29770
+ translationFn
29771
+ });
28251
29772
  case "node-select": return /* @__PURE__ */ jsx(NodeSelectField, {
28252
29773
  field,
28253
29774
  value,
@@ -30020,47 +31541,6 @@ var Separator = forwardRef(({ className, orientation, ...props }, ref) => {
30020
31541
  });
30021
31542
  Separator.displayName = "Separator";
30022
31543
  //#endregion
30023
- //#region src/primitives/input.tsx
30024
- /**
30025
- * `disabled:opacity-50 disabled:cursor-not-allowed` for the same reason as
30026
- * `Select` — an explicit `bg-surface` / `text-foreground` overrides the greying
30027
- * a browser would otherwise apply, leaving an inert field indistinguishable
30028
- * from a live one. See `disabled-affordance.test.tsx`.
30029
- */
30030
- var inputVariants = cva("h-7 w-full px-2.5 text-xs bg-surface border rounded-md text-foreground placeholder:text-foreground-subtle focus:outline-none focus:ring-1 focus:ring-primary transition-colors disabled:opacity-50 disabled:cursor-not-allowed", {
30031
- variants: { state: {
30032
- default: "border-border",
30033
- error: "border-danger"
30034
- } },
30035
- defaultVariants: { state: "default" }
30036
- });
30037
- var Input = forwardRef(({ className, state, leftSlot, rightSlot, ...props }, ref) => {
30038
- if (leftSlot || rightSlot) return /* @__PURE__ */ jsxs("div", {
30039
- className: "relative flex items-center w-full",
30040
- children: [
30041
- leftSlot && /* @__PURE__ */ jsx("div", {
30042
- className: "absolute left-2.5 flex items-center pointer-events-none",
30043
- children: leftSlot
30044
- }),
30045
- /* @__PURE__ */ jsx("input", {
30046
- ref,
30047
- className: cn(inputVariants({ state }), leftSlot ? "pl-7" : "", rightSlot ? "pr-7" : "", className),
30048
- ...props
30049
- }),
30050
- rightSlot && /* @__PURE__ */ jsx("div", {
30051
- className: "absolute right-2.5 flex items-center pointer-events-none",
30052
- children: rightSlot
30053
- })
30054
- ]
30055
- });
30056
- return /* @__PURE__ */ jsx("input", {
30057
- ref,
30058
- className: cn(inputVariants({ state }), className),
30059
- ...props
30060
- });
30061
- });
30062
- Input.displayName = "Input";
30063
- //#endregion
30064
31544
  //#region src/primitives/checkbox.tsx
30065
31545
  var Checkbox = forwardRef(({ className, ...props }, ref) => {
30066
31546
  return /* @__PURE__ */ jsx("input", {
@@ -30093,204 +31573,6 @@ var Switch = forwardRef(({ className, checked, onCheckedChange, label, ...props
30093
31573
  });
30094
31574
  Switch.displayName = "Switch";
30095
31575
  //#endregion
30096
- //#region src/primitives/dialog.tsx
30097
- var DialogContext = createContext(null);
30098
- function useDialogContext() {
30099
- const ctx = useContext(DialogContext);
30100
- if (!ctx) throw new Error("Dialog compound components must be used within <Dialog>");
30101
- return ctx;
30102
- }
30103
- function Dialog({ children, open: controlledOpen, onOpenChange }) {
30104
- const [uncontrolledOpen, setUncontrolledOpen] = useState(false);
30105
- const open = controlledOpen ?? uncontrolledOpen;
30106
- const contentId = useId();
30107
- const setOpen = useCallback((next) => {
30108
- onOpenChange?.(next);
30109
- if (controlledOpen === void 0) setUncontrolledOpen(next);
30110
- }, [controlledOpen, onOpenChange]);
30111
- return /* @__PURE__ */ jsx(DialogContext.Provider, {
30112
- value: {
30113
- open,
30114
- setOpen,
30115
- contentId
30116
- },
30117
- children
30118
- });
30119
- }
30120
- function DialogTrigger({ children, ...props }) {
30121
- const { setOpen } = useDialogContext();
30122
- return /* @__PURE__ */ jsx("button", {
30123
- type: "button",
30124
- onClick: () => setOpen(true),
30125
- ...props,
30126
- children
30127
- });
30128
- }
30129
- /**
30130
- * A native <dialog> opened with showModal() is promoted to the top layer, so
30131
- * ancestor transform/overflow can't clip it — but it relies on the UA
30132
- * stylesheet's `dialog { margin: auto }` for modal centering. Tailwind's
30133
- * preflight resets `margin: 0` on every element, which defeats that centering
30134
- * and pins the modal to the top-left. We therefore center deterministically:
30135
- * a top-layer dialog with `position: fixed` is viewport-relative, and a
30136
- * self-transform is fine, so `fixed left-1/2 top-1/2 -translate-x-1/2
30137
- * -translate-y-1/2 m-0` always centers regardless of any margin reset. Width
30138
- * is capped to the viewport (`w-[calc(100vw-2rem)]`) rather than a bare
30139
- * `w-full` (which would overflow/clip a centered dialog); the per-variant
30140
- * `max-w-*` keeps small dialogs small while the cap prevents overflow.
30141
- *
30142
- * Height is capped against the SAFE viewport, not the raw one. A top-layer
30143
- * `fixed` dialog is viewport-relative, and under `viewport-fit=cover` the
30144
- * viewport extends behind the iOS notch/Dynamic Island and home indicator —
30145
- * so a dialog tall enough to hit its `max-h` put its header under the clock.
30146
- * Because the box is centred on the viewport (not on the safe area), the cap
30147
- * has to subtract TWICE the LARGER of the two insets for the box to clear
30148
- * both edges; using each inset once would still overflow the taller side
30149
- * whenever they are asymmetric (they are: ~59px top vs ~34px bottom). Every
30150
- * term collapses to the previous `100dvh - 2rem` where the insets are 0.
30151
- */
30152
- var contentVariants = cva("fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 m-0 w-[calc(100vw-2rem)] bg-background-elevated text-foreground border border-border rounded-lg p-4 backdrop:bg-black/50 backdrop:backdrop-blur-sm max-h-[calc(100dvh-2rem-2*max(env(safe-area-inset-top),env(safe-area-inset-bottom)))] [&:not([open])]:hidden", {
30153
- variants: {
30154
- width: {
30155
- sm: "max-w-sm",
30156
- md: "max-w-md",
30157
- lg: "max-w-lg"
30158
- },
30159
- /**
30160
- * WHICH box scrolls when the content is taller than the height cap.
30161
- *
30162
- * `content` (default) scrolls the whole dialog: the title, any tab strip
30163
- * and the Save/Cancel row travel with the fields. That is fine for a
30164
- * short form and wrong for a long one — the operator scrolls to the
30165
- * bottom of a rule editor and the buttons are somewhere above him, the
30166
- * tab strip is gone, and the only way back to either is to scroll the
30167
- * form he was reading.
30168
- *
30169
- * `body` makes the dialog a flex column that does not scroll at all and
30170
- * hands the scrolling to `<DialogBody>`. The chrome around the body —
30171
- * header, tab strip, footer — stays put at every height. The dialog
30172
- * keeps its own padding either way, so `sheet-on-mobile`'s
30173
- * safe-area bottom padding still applies to the footer.
30174
- *
30175
- * `body` also takes a DEFINITE height below `md` — see the
30176
- * `scroll: 'body'` compound variants. That is not a style choice; the
30177
- * flex chain does not resolve without it on iOS Safari.
30178
- */
30179
- scroll: {
30180
- content: "overflow-y-auto",
30181
- body: "flex flex-col overflow-hidden"
30182
- },
30183
- placement: {
30184
- center: "",
30185
- "sheet-on-mobile": "bottom-0 left-0 right-0 top-auto w-full max-w-full translate-x-0 translate-y-0 rounded-b-none max-h-[92dvh] pb-[calc(1rem+env(safe-area-inset-bottom))] md:left-1/2 md:top-1/2 md:-translate-x-1/2 md:-translate-y-1/2 md:rounded-lg md:bottom-auto md:pb-4 md:max-h-[calc(100dvh-2rem-2*max(env(safe-area-inset-top),env(safe-area-inset-bottom)))]"
30186
- }
30187
- },
30188
- compoundVariants: [
30189
- {
30190
- placement: "sheet-on-mobile",
30191
- width: "sm",
30192
- class: "md:w-[calc(100vw-2rem)] md:max-w-sm"
30193
- },
30194
- {
30195
- placement: "sheet-on-mobile",
30196
- width: "md",
30197
- class: "md:w-[calc(100vw-2rem)] md:max-w-md"
30198
- },
30199
- {
30200
- placement: "sheet-on-mobile",
30201
- width: "lg",
30202
- class: "md:w-[calc(100vw-2rem)] md:max-w-lg"
30203
- },
30204
- {
30205
- scroll: "body",
30206
- placement: "sheet-on-mobile",
30207
- class: "h-[92dvh] md:h-auto"
30208
- },
30209
- {
30210
- scroll: "body",
30211
- placement: "center",
30212
- class: "h-[calc(100dvh-2rem-2*max(env(safe-area-inset-top),env(safe-area-inset-bottom)))] md:h-auto"
30213
- }
30214
- ],
30215
- defaultVariants: {
30216
- width: "md",
30217
- placement: "center",
30218
- scroll: "content"
30219
- }
30220
- });
30221
- var DialogContent = forwardRef(({ className, width, placement, scroll, children, ...props }, ref) => {
30222
- const { open, setOpen, contentId } = useDialogContext();
30223
- const innerRef = useRef(null);
30224
- const dialogRef = ref ?? innerRef;
30225
- useEffect(() => {
30226
- const el = dialogRef.current;
30227
- if (!el) return;
30228
- if (open && !el.open) el.showModal();
30229
- if (!open && el.open) el.close();
30230
- }, [open, dialogRef]);
30231
- return /* @__PURE__ */ jsx("dialog", {
30232
- ref: dialogRef,
30233
- id: contentId,
30234
- className: cn(contentVariants({
30235
- width,
30236
- placement,
30237
- scroll
30238
- }), className),
30239
- onClose: () => setOpen(false),
30240
- ...props,
30241
- children
30242
- });
30243
- });
30244
- DialogContent.displayName = "DialogContent";
30245
- /** `shrink-0` is inert outside a flex column and load-bearing inside one:
30246
- * under `scroll="body"` the header must keep its height while the body gives
30247
- * ground, or a long form squeezes its own title to nothing. */
30248
- function DialogHeader({ className, ...props }) {
30249
- return /* @__PURE__ */ jsx("div", {
30250
- className: cn("flex flex-col gap-1 mb-3 shrink-0", className),
30251
- ...props
30252
- });
30253
- }
30254
- /**
30255
- * The scrolling middle of a `scroll="body"` dialog.
30256
- *
30257
- * `min-h-0` is what makes it work: a flex item's default `min-height: auto`
30258
- * refuses to shrink below its content, so without it the body pushes the
30259
- * footer out of the dialog instead of scrolling — the exact failure this
30260
- * variant exists to remove. Anything sticky INSIDE a dialog body pins against
30261
- * this element, since it is the scroll container.
30262
- *
30263
- * It only bounds a scroll area if every ancestor up to the dialog has a
30264
- * resolved height. `DialogContent` supplies the top of that chain (see the
30265
- * `scroll: 'body'` compound variants); an intermediate wrapper between them —
30266
- * a `<Tabs>` that owns the tab strip, say — must carry `flex-1 min-h-0` too,
30267
- * or it collapses and takes the body with it.
30268
- */
30269
- function DialogBody({ className, ...props }) {
30270
- return /* @__PURE__ */ jsx("div", {
30271
- className: cn("flex-1 min-h-0 overflow-y-auto", className),
30272
- ...props
30273
- });
30274
- }
30275
- function DialogFooter({ className, ...props }) {
30276
- return /* @__PURE__ */ jsx("div", {
30277
- className: cn("flex justify-end gap-2 mt-4 shrink-0", className),
30278
- ...props
30279
- });
30280
- }
30281
- function DialogTitle({ className, ...props }) {
30282
- return /* @__PURE__ */ jsx("h2", {
30283
- className: cn("text-sm font-semibold text-foreground", className),
30284
- ...props
30285
- });
30286
- }
30287
- function DialogDescription({ className, ...props }) {
30288
- return /* @__PURE__ */ jsx("p", {
30289
- className: cn("text-xs text-foreground-muted", className),
30290
- ...props
30291
- });
30292
- }
30293
- //#endregion
30294
31576
  //#region src/primitives/tooltip.tsx
30295
31577
  var TooltipContext = createContext(null);
30296
31578
  function useTooltipContext() {
@@ -49397,1248 +50679,683 @@ function SwitchInlineControl({ trpc, deviceId, features, optimistic }) {
49397
50679
  optimistic
49398
50680
  });
49399
50681
  }
49400
- function useLockCapGuard(trpc, deviceId) {
49401
- const { cap } = useDeviceCapability({
49402
- trpc,
49403
- deviceId,
49404
- cap: (d) => d.lockControl,
49405
- slice: (d) => d.state.lockControl
49406
- });
49407
- return cap !== void 0;
49408
- }
49409
- function LockInlineControl({ trpc, deviceId, features, optimistic }) {
49410
- if (!useLockCapGuard(trpc, deviceId)) return null;
49411
- return /* @__PURE__ */ jsx(LockPanel, {
49412
- trpc,
49413
- deviceId,
49414
- features,
49415
- layout: "compact",
49416
- optimistic
49417
- });
49418
- }
49419
- function LightInlineControl({ trpc, deviceId, features, optimistic }) {
49420
- if (!useSwitchCapGuard(trpc, deviceId)) return null;
49421
- return /* @__PURE__ */ jsxs("div", {
49422
- className: "inline-flex items-center gap-2",
49423
- children: [/* @__PURE__ */ jsx(SwitchPanel, {
49424
- trpc,
49425
- deviceId,
49426
- features,
49427
- layout: "compact",
49428
- optimistic
49429
- }), /* @__PURE__ */ jsx(BrightnessPanel, {
49430
- trpc,
49431
- deviceId,
49432
- features,
49433
- layout: "compact",
49434
- optimistic
49435
- })]
49436
- });
49437
- }
49438
- function useFanCapGuard(trpc, deviceId) {
49439
- const { cap } = useDeviceCapability({
49440
- trpc,
49441
- deviceId,
49442
- cap: (d) => d.fanControl,
49443
- slice: (d) => d.state.fanControl
49444
- });
49445
- return cap !== void 0;
49446
- }
49447
- function FanInlineControl({ trpc, deviceId, features, optimistic }) {
49448
- if (!useFanCapGuard(trpc, deviceId)) return null;
49449
- return /* @__PURE__ */ jsxs("div", {
49450
- className: "inline-flex items-center gap-2",
49451
- children: [/* @__PURE__ */ jsx(SwitchPanel, {
49452
- trpc,
49453
- deviceId,
49454
- features,
49455
- layout: "compact",
49456
- optimistic
49457
- }), /* @__PURE__ */ jsx(FanPanel, {
49458
- trpc,
49459
- deviceId,
49460
- features,
49461
- layout: "compact",
49462
- optimistic
49463
- })]
49464
- });
49465
- }
49466
- function useClimateCapGuard(trpc, deviceId) {
49467
- const { cap } = useDeviceCapability({
49468
- trpc,
49469
- deviceId,
49470
- cap: (d) => d.climateControl,
49471
- slice: (d) => d.state.climateControl
49472
- });
49473
- return cap !== void 0;
49474
- }
49475
- function ThermostatInlineControl({ trpc, deviceId, features, optimistic }) {
49476
- if (!useClimateCapGuard(trpc, deviceId)) return null;
49477
- return /* @__PURE__ */ jsx(ClimatePanel, {
49478
- trpc,
49479
- deviceId,
49480
- features,
49481
- layout: "compact",
49482
- optimistic
49483
- });
49484
- }
49485
- function AcInlineControl({ trpc, deviceId, features, optimistic }) {
49486
- if (!useClimateCapGuard(trpc, deviceId)) return null;
49487
- return /* @__PURE__ */ jsx(AcPanel, {
49488
- trpc,
49489
- deviceId,
49490
- features,
49491
- layout: "compact",
49492
- optimistic
49493
- });
49494
- }
49495
- function AcHeroCard({ trpc, deviceId, features, optimistic }) {
49496
- if (!useClimateCapGuard(trpc, deviceId)) return null;
49497
- return /* @__PURE__ */ jsx(AcPanel, {
49498
- trpc,
49499
- deviceId,
49500
- features,
49501
- layout: "full",
49502
- optimistic
49503
- });
49504
- }
49505
- function useMediaPlayerCapGuard(trpc, deviceId) {
49506
- const { cap } = useDeviceCapability({
49507
- trpc,
49508
- deviceId,
49509
- cap: (d) => d.mediaPlayer,
49510
- slice: (d) => d.state.mediaPlayer
49511
- });
49512
- return cap !== void 0;
49513
- }
49514
- function MediaPlayerInlineControl({ trpc, deviceId, features, optimistic }) {
49515
- if (!useMediaPlayerCapGuard(trpc, deviceId)) return null;
49516
- return /* @__PURE__ */ jsx(MediaPlayerPanel, {
49517
- trpc,
49518
- deviceId,
49519
- features,
49520
- layout: "compact",
49521
- optimistic
49522
- });
49523
- }
49524
- function useControlCapGuard(trpc, deviceId) {
49525
- const { cap } = useDeviceCapability({
49526
- trpc,
49527
- deviceId,
49528
- cap: (d) => d.control,
49529
- slice: (d) => d.state.control
49530
- });
49531
- return cap !== void 0;
49532
- }
49533
- function ControlInlineControl({ trpc, deviceId, features, optimistic }) {
49534
- if (!useControlCapGuard(trpc, deviceId)) return null;
49535
- return /* @__PURE__ */ jsx(ControlPanel, {
49536
- trpc,
49537
- deviceId,
49538
- features,
49539
- layout: "compact",
49540
- optimistic
49541
- });
49542
- }
49543
- function ControlHeroCard({ trpc, deviceId, features, optimistic }) {
49544
- if (!useControlCapGuard(trpc, deviceId)) return null;
49545
- return /* @__PURE__ */ jsx(HeroShell, { children: /* @__PURE__ */ jsx(ControlPanel, {
49546
- trpc,
49547
- deviceId,
49548
- features,
49549
- layout: "full",
49550
- optimistic
49551
- }) });
49552
- }
49553
- //#endregion
49554
- //#region src/composites/device-controls/control-registry.tsx
49555
- /**
49556
- * Presentation binding: the shared `DeviceControlKind` (decided in
49557
- * `@camstack/types` `resolveDeviceControlKind`) → this platform's web
49558
- * components. Exhaustive over `DeviceControlKind` — omitting a kind fails the
49559
- * build here. The type→kind DECISION is single-sourced in `@camstack/types`;
49560
- * this map only binds a kind to its web widgets.
49561
- */
49562
- var CONTROL_KIND_COMPONENTS = {
49563
- cover: {
49564
- InlineControl: CoverInlineControl,
49565
- HeroCard: CoverHeroCard
49566
- },
49567
- valve: {
49568
- InlineControl: ValveInlineControl,
49569
- HeroCard: ValveHeroCard
49570
- },
49571
- humidifier: {
49572
- InlineControl: HumidifierInlineControl,
49573
- HeroCard: HumidifierHeroCard
49574
- },
49575
- "water-heater": {
49576
- InlineControl: WaterHeaterInlineControl,
49577
- HeroCard: WaterHeaterHeroCard
49578
- },
49579
- switch: {
49580
- InlineControl: SwitchInlineControl,
49581
- HeroCard: SwitchHeroCard
49582
- },
49583
- light: {
49584
- InlineControl: LightInlineControl,
49585
- HeroCard: LightHeroCard
49586
- },
49587
- fan: {
49588
- InlineControl: FanInlineControl,
49589
- HeroCard: FanHeroCard
49590
- },
49591
- sensor: {
49592
- InlineControl: SensorInlineControl,
49593
- HeroCard: SensorHeroCard
49594
- },
49595
- thermostat: {
49596
- InlineControl: ThermostatInlineControl,
49597
- HeroCard: ThermostatHeroCard
49598
- },
49599
- climate: {
49600
- InlineControl: AcInlineControl,
49601
- HeroCard: AcHeroCard
49602
- },
49603
- button: {
49604
- InlineControl: ButtonControl,
49605
- HeroCard: ButtonHeroCard
49606
- },
49607
- update: {
49608
- InlineControl: UpdateInlineControl,
49609
- HeroCard: UpdateHeroCard
49610
- },
49611
- lock: {
49612
- InlineControl: LockInlineControl,
49613
- HeroCard: LockHeroCard
49614
- },
49615
- "media-player": {
49616
- InlineControl: MediaPlayerInlineControl,
49617
- HeroCard: MediaPlayerHeroCard
49618
- },
49619
- alarm: {
49620
- InlineControl: AlarmInlineControl,
49621
- HeroCard: AlarmHeroCard
49622
- },
49623
- control: {
49624
- InlineControl: ControlInlineControl,
49625
- HeroCard: ControlHeroCard
49626
- },
49627
- weather: {
49628
- InlineControl: WeatherInlineControl,
49629
- HeroCard: WeatherHeroCard
49630
- },
49631
- vacuum: {
49632
- InlineControl: VacuumInlineControl,
49633
- HeroCard: VacuumHeroCard
49634
- },
49635
- "lawn-mower": {
49636
- InlineControl: LawnMowerInlineControl,
49637
- HeroCard: LawnMowerHeroCard
49638
- },
49639
- image: {
49640
- InlineControl: ImageInlineControl,
49641
- HeroCard: ImageHeroCard
49642
- },
49643
- "pet-feeder": {
49644
- InlineControl: PetFeederInlineControl,
49645
- HeroCard: PetFeederHeroCard
49646
- },
49647
- script: {
49648
- InlineControl: ScriptInlineControl,
49649
- HeroCard: ScriptHeroCard
49650
- },
49651
- automation: {
49652
- InlineControl: AutomationInlineControl,
49653
- HeroCard: AutomationHeroCard
49654
- },
49655
- dummy: {
49656
- InlineControl: DummyInline,
49657
- HeroCard: DummyHeroCard
49658
- }
49659
- };
49660
- /**
49661
- * Exhaustive record over DeviceType, DERIVED from the shared decision
49662
- * (`DEVICE_TYPE_CONTROL_KIND`) composed with the web `CONTROL_KIND_COMPONENTS`
49663
- * binding — so the type→control mapping can never diverge from the single
49664
- * source in `@camstack/types`. Preserved as a public export for consumers/tests
49665
- * that index it by `DeviceType`.
49666
- *
49667
- * The `Object.fromEntries` result is a `{ [k: string]: DeviceTypeControl }`;
49668
- * `Object.values(DeviceType)` (a string enum) yields exactly the enum's members,
49669
- * so the record is fully populated — the assertion narrows that documented
49670
- * enum-record projection boundary.
49671
- */
49672
- var DEVICE_TYPE_CONTROL = Object.fromEntries(Object.values(DeviceType).map((type) => [type, CONTROL_KIND_COMPONENTS[DEVICE_TYPE_CONTROL_KIND[type]]]));
49673
- /**
49674
- * Per-type FILL flag.
49675
- *
49676
- * true — range / choice / button controls (cover ▲■▼ triad, thermostat
49677
- * setpoint stepper, media/control/button action buttons, alarm
49678
- * segmented control). The child STRETCHES to the full column width.
49679
- * false — readouts, tap-toggles, icon controls (lock), and dummy/placeholder
49680
- * types. The child keeps its NATURAL width; where it sits in the
49681
- * column (flush right vs centered) is decided by the caller via a
49682
- * `ControlPlacement` — see `resolveControlAlign`.
49683
- *
49684
- * Dummy types (Camera, Hub, Generic, Weather, Vacuum, Container, and placeholder
49685
- * types Notifier/Script/Automation whose InlineControl renders "—") are `false` —
49686
- * the dash placeholder must not be stretched to the full column width. (AlarmPanel
49687
- * has a real segmented inline control and is `true`.) When a later task gives a
49688
- * remaining placeholder type a real stretchy inline control, flip its flag to true.
49689
- *
49690
- * Exhaustive over DeviceType — a missing member is a TypeScript build error.
49691
- */
49692
- var CONTROL_FILLS = {
49693
- [DeviceType.Cover]: true,
49694
- [DeviceType.Valve]: true,
49695
- [DeviceType.Thermostat]: true,
49696
- [DeviceType.Climate]: true,
49697
- [DeviceType.Control]: true,
49698
- [DeviceType.Button]: true,
49699
- [DeviceType.MediaPlayer]: true,
49700
- [DeviceType.AlarmPanel]: true,
49701
- [DeviceType.Lock]: false,
49702
- [DeviceType.Vacuum]: false,
49703
- [DeviceType.LawnMower]: false,
49704
- [DeviceType.Humidifier]: false,
49705
- [DeviceType.Switch]: false,
49706
- [DeviceType.Siren]: false,
49707
- [DeviceType.Light]: false,
49708
- [DeviceType.Fan]: false,
49709
- [DeviceType.WaterHeater]: false,
49710
- [DeviceType.Sensor]: false,
49711
- [DeviceType.Presence]: false,
49712
- [DeviceType.EventEmitter]: false,
49713
- [DeviceType.Notifier]: false,
49714
- [DeviceType.Script]: false,
49715
- [DeviceType.Automation]: false,
49716
- [DeviceType.Camera]: false,
49717
- [DeviceType.Hub]: false,
49718
- [DeviceType.Update]: false,
49719
- [DeviceType.Generic]: false,
49720
- [DeviceType.Weather]: false,
49721
- [DeviceType.Container]: false,
49722
- [DeviceType.Image]: false,
49723
- [DeviceType.PetFeeder]: false
49724
- };
49725
- var DEVICE_TYPE_VALUES$1 = new Set(Object.values(DeviceType));
49726
- /**
49727
- * Runtime-string-safe lookup: a device row's `type` crosses the wire as a plain
49728
- * string. The type→control-kind DECISION is single-sourced in `@camstack/types`
49729
- * (`resolveDeviceControlKind`, null for an unrecognised type); this maps the
49730
- * resolved kind onto the web components.
49731
- */
49732
- function resolveDeviceControl(type) {
49733
- const kind = resolveDeviceControlKind(type);
49734
- return kind ? CONTROL_KIND_COMPONENTS[kind] : null;
49735
- }
49736
- /**
49737
- * Runtime-string-safe alignment lookup for the fixed-width control column.
49738
- *
49739
- * A device's `type` crosses the wire as a plain string;
49740
- * `DEVICE_TYPE_VALUES.has(type)` narrows it to `DeviceType` before indexing
49741
- * `CONTROL_FILLS`, making the cast sound. The `'table'` placement is
49742
- * content-width for EVERY type (returns `FIT`), so a wide control never
49743
- * overflows a fixed box — the FILL flag is irrelevant there. For the fixed-box
49744
- * placements a FILL type always stretches (`FILL`) and a natural-width type is
49745
- * placed by the caller's `placement` — `'center'` → `CENTER`, `'right'` →
49746
- * `RIGHT` (card / minimal view). An unrecognised type falls back to
49747
- * natural-width placement.
49748
- */
49749
- function resolveControlAlign(type, placement) {
49750
- if (placement === "table") return "fit";
49751
- return DEVICE_TYPE_VALUES$1.has(type) && CONTROL_FILLS[type] ? FILL : placement === "center" ? CENTER : RIGHT;
49752
- }
49753
- //#endregion
49754
- //#region src/lib/format-last-seen.ts
49755
- /**
49756
- * Relative "last seen" formatter for the offline affordance.
49757
- *
49758
- * Renders a short relative-time label from a millisecond epoch:
49759
- * "just now", "5m ago", "3h ago", "2d ago". Returns `null` for missing
49760
- * inputs, or inputs below {@link MIN_PLAUSIBLE_TIMESTAMP_MS} (a
49761
- * placeholder or a seeded "known value, unknown change time" stamp), so
49762
- * callers can render nothing rather than a misleading "—", "NaN ago", or
49763
- * (for a seeded `1`) "20000 days ago".
49764
- *
49765
- * Future timestamps (clock skew between the device and the browser)
49766
- * collapse to "just now" instead of a negative duration.
49767
- */
49768
- var SECOND_MS = 1e3;
49769
- var MINUTE_MS = 60 * SECOND_MS;
49770
- var HOUR_MS = 60 * MINUTE_MS;
49771
- var DAY_MS = 24 * HOUR_MS;
49772
- var JUST_NOW_MS = 30 * SECOND_MS;
49773
- /**
49774
- * The earliest timestamp a real "last seen" could plausibly carry. Below
49775
- * this, a value is a placeholder or a seeded "known value, unknown change
49776
- * time" stamp (e.g. Homematic's `HM_SEEDED_CHANGED_AT = 1`) — never a real
49777
- * instant. Year 2000 predates every device this renders.
49778
- */
49779
- var MIN_PLAUSIBLE_TIMESTAMP_MS = Date.UTC(2e3, 0, 1);
49780
- function formatLastSeen(timestampMs) {
49781
- if (timestampMs === null || timestampMs === void 0 || timestampMs < MIN_PLAUSIBLE_TIMESTAMP_MS) return null;
49782
- const delta = Date.now() - timestampMs;
49783
- if (delta < JUST_NOW_MS) return "just now";
49784
- if (delta < HOUR_MS) return `${String(Math.floor(delta / MINUTE_MS))}m ago`;
49785
- if (delta < DAY_MS) return `${String(Math.floor(delta / HOUR_MS))}h ago`;
49786
- return `${String(Math.floor(delta / DAY_MS))}d ago`;
49787
- }
49788
- //#endregion
49789
- //#region src/composites/device-controls/offline-badge.tsx
49790
- function OfflineBadge({ lastChangedAt, className }) {
49791
- const lastSeen = formatLastSeen(lastChangedAt);
49792
- return /* @__PURE__ */ jsxs("span", {
49793
- title: lastSeen === null ? "Unavailable" : `Unavailable · last seen ${lastSeen}`,
49794
- className: cn("inline-flex items-center gap-1 rounded-full px-2 py-0.5", "bg-surface-hover text-foreground-subtle text-[10px] font-medium", "border border-border", className),
49795
- children: [
49796
- /* @__PURE__ */ jsx(WifiOff, {
49797
- className: "h-3 w-3 flex-shrink-0",
49798
- "aria-hidden": "true"
49799
- }),
49800
- /* @__PURE__ */ jsx("span", { children: "Unavailable" }),
49801
- lastSeen !== null ? /* @__PURE__ */ jsx("span", {
49802
- className: "text-foreground-subtle/70",
49803
- children: `· last seen ${lastSeen}`
49804
- }) : null
49805
- ]
49806
- });
49807
- }
49808
- //#endregion
49809
- //#region src/composites/device-controls/primary-child.ts
49810
- /**
49811
- * Which child a container is rendered AS — this surface's name for the shared
49812
- * rule.
49813
- *
49814
- * The list and the resolution used to live here, in a COPY that carried a
49815
- * comment asking the Home Assistant adoption mapping and the viewer's panel to
49816
- * stay in sync with it. Three lists and three comments is not one rule, it is
49817
- * three chances to drift. Both now come from `@camstack/types`, which all of
49818
- * them already depend on; what remains here is the adapter for this module's
49819
- * historical `ChildRef` shape.
49820
- */
49821
- /** The shared precedence, under this module's historical name. */
49822
- var PRIORITY = CONTAINER_CHILD_PRIORITY;
49823
- /**
49824
- * Resolve a container's primary child: the operator's pick when it still
49825
- * matches a live child, else the highest-priority type.
49826
- *
49827
- * `entityId` here is `sourceInfo?.id ?? stableId` (see `containerChildToRef`).
49828
- * The shared resolver matches its canonical `stableId` key first and accepts
49829
- * that legacy value too, so a pick stored under either keeps resolving.
49830
- */
49831
- function resolvePrimaryChild(children, overrideEntityId, containerName) {
49832
- const picked = resolveContainerPrimaryChild(children.map((c) => ({
49833
- id: c.id,
49834
- stableId: c.entityId,
49835
- type: c.type,
49836
- ...c.role !== void 0 ? { role: c.role } : {},
49837
- ...c.name !== void 0 ? { name: c.name } : {}
49838
- })), overrideEntityId, containerName);
49839
- return picked === null ? null : children.find((c) => c.id === picked.id) ?? null;
49840
- }
49841
- //#endregion
49842
- //#region src/composites/device-controls/container-primary.ts
49843
- /**
49844
- * Re-sync-stable identity of a child. Prefers the upstream-system dispatch key
49845
- * (`sourceInfo.id` — e.g. HA `entity_id`); falls back to `stableId` for
49846
- * providers that never populated SourceInfo. Both survive a re-sync that may
49847
- * reallocate the numeric `id`.
49848
- */
49849
- function childEntityId(child) {
49850
- return child.sourceInfo?.id ?? child.stableId;
49851
- }
49852
- /** Project a raw child row onto the pure resolver's {@link ChildRef} shape. */
49853
- function containerChildToRef(child) {
49854
- return {
49855
- id: child.id,
49856
- entityId: childEntityId(child),
49857
- type: child.type,
49858
- ...child.role !== void 0 && child.role !== null ? { role: child.role } : {},
49859
- ...child.name !== void 0 ? { name: child.name } : {}
49860
- };
49861
- }
49862
- /**
49863
- * Resolve the entityId override from the container's persisted `linkDeviceId`.
49864
- * Returns the linked child's stable entityId, or `undefined` when there is no
49865
- * link or the link is stale (points to a numeric id no longer present) — in
49866
- * which case the resolver falls back to the priority default.
49867
- */
49868
- function overrideEntityIdFromLink(children, linkDeviceId) {
49869
- if (linkDeviceId === null || linkDeviceId === void 0) return void 0;
49870
- const linked = children.find((c) => c.id === linkDeviceId);
49871
- return linked === void 0 ? void 0 : childEntityId(linked);
49872
- }
49873
- /**
49874
- * Full container → primary-child resolution: project children to refs, resolve
49875
- * the override entityId, and delegate to the pure resolver. Returns `null` for a
49876
- * container with no children (caller degrades gracefully).
49877
- *
49878
- * Override precedence:
49879
- * 1. `primaryChildEntityId` — the DURABLE override (re-sync/rename-stable),
49880
- * passed straight to the resolver which matches on `entityId`. Wins when a
49881
- * non-empty string is supplied.
49882
- * 2. `linkDeviceId` — LEGACY numeric override, re-derived to an entityId from
49883
- * the live child set. Consulted only when there is no durable override.
49884
- *
49885
- * When neither resolves to a live child, the resolver falls back to the
49886
- * priority default.
49887
- */
49888
- function resolveContainerPrimary(children, linkDeviceId, primaryChildEntityId) {
49889
- return resolvePrimaryChild(children.map(containerChildToRef), primaryChildEntityId !== null && primaryChildEntityId !== void 0 && primaryChildEntityId !== "" ? primaryChildEntityId : overrideEntityIdFromLink(children, linkDeviceId));
49890
- }
49891
- //#endregion
49892
- //#region src/composites/device-controls/container-primary-hero.tsx
49893
- function ContainerPrimaryHero({ trpc, children, linkDeviceId, primaryChildEntityId }) {
49894
- const primary = resolveContainerPrimary(children, linkDeviceId, primaryChildEntityId);
49895
- const primaryChild = primary === null ? void 0 : children.find((c) => c.id === primary.id);
49896
- if (primaryChild === void 0) return /* @__PURE__ */ jsx("div", {
49897
- className: "flex h-full w-full items-center justify-center p-4 text-[11px] italic text-foreground-subtle",
49898
- children: "No child entities to display."
50682
+ function useLockCapGuard(trpc, deviceId) {
50683
+ const { cap } = useDeviceCapability({
50684
+ trpc,
50685
+ deviceId,
50686
+ cap: (d) => d.lockControl,
50687
+ slice: (d) => d.state.lockControl
49899
50688
  });
49900
- return /* @__PURE__ */ jsx("div", {
49901
- className: "flex h-full w-full flex-col items-stretch gap-3 p-3 sm:p-4",
49902
- children: /* @__PURE__ */ jsx(resolveDeviceControl(primaryChild.type)?.HeroCard ?? DummyHeroCard, {
50689
+ return cap !== void 0;
50690
+ }
50691
+ function LockInlineControl({ trpc, deviceId, features, optimistic }) {
50692
+ if (!useLockCapGuard(trpc, deviceId)) return null;
50693
+ return /* @__PURE__ */ jsx(LockPanel, {
50694
+ trpc,
50695
+ deviceId,
50696
+ features,
50697
+ layout: "compact",
50698
+ optimistic
50699
+ });
50700
+ }
50701
+ function LightInlineControl({ trpc, deviceId, features, optimistic }) {
50702
+ if (!useSwitchCapGuard(trpc, deviceId)) return null;
50703
+ return /* @__PURE__ */ jsxs("div", {
50704
+ className: "inline-flex items-center gap-2",
50705
+ children: [/* @__PURE__ */ jsx(SwitchPanel, {
49903
50706
  trpc,
49904
- deviceId: primaryChild.id,
49905
- features: primaryChild.features
49906
- })
50707
+ deviceId,
50708
+ features,
50709
+ layout: "compact",
50710
+ optimistic
50711
+ }), /* @__PURE__ */ jsx(BrightnessPanel, {
50712
+ trpc,
50713
+ deviceId,
50714
+ features,
50715
+ layout: "compact",
50716
+ optimistic
50717
+ })]
49907
50718
  });
49908
50719
  }
49909
- //#endregion
49910
- //#region src/composites/device-controls/primary-child-picker.tsx
49911
- var BASE_SELECT_CLASS = "rounded-md border border-border bg-surface px-1.5 py-0.5 text-[11px] text-foreground";
49912
- function PrimaryChildPicker({ childrenList, selectedId, onSetPrimary, disabled, className }) {
49913
- return /* @__PURE__ */ jsx("select", {
49914
- value: selectedId,
49915
- disabled,
49916
- onChange: (e) => {
49917
- const nextId = Number(e.target.value);
49918
- if (!Number.isFinite(nextId) || nextId === selectedId) return;
49919
- const picked = childrenList.find((c) => c.id === nextId);
49920
- if (picked !== void 0) onSetPrimary(picked.stableId);
49921
- },
49922
- className: className === void 0 ? BASE_SELECT_CLASS : `${BASE_SELECT_CLASS} ${className}`,
49923
- children: childrenList.map((c) => /* @__PURE__ */ jsxs("option", {
49924
- value: c.id,
49925
- children: [
49926
- c.name,
49927
- " (",
49928
- c.type,
49929
- ")"
49930
- ]
49931
- }, c.id))
50720
+ function useFanCapGuard(trpc, deviceId) {
50721
+ const { cap } = useDeviceCapability({
50722
+ trpc,
50723
+ deviceId,
50724
+ cap: (d) => d.fanControl,
50725
+ slice: (d) => d.state.fanControl
49932
50726
  });
50727
+ return cap !== void 0;
49933
50728
  }
49934
- //#endregion
49935
- //#region src/composites/device-list/batch-toolbar.tsx
49936
- function DeviceBatchToolbar({ selectedIds, onClear, remove, disable, enable }) {
49937
- const [pendingDelete, setPendingDelete] = useState(false);
49938
- if (selectedIds.size === 0) return null;
49939
- const ids = [...selectedIds];
49940
- const count = ids.length;
49941
- const handleDisable = () => {
49942
- for (const id of ids) disable({ deviceId: id });
49943
- onClear();
49944
- };
49945
- const handleEnable = () => {
49946
- for (const id of ids) enable({ deviceId: id });
49947
- onClear();
49948
- };
49949
- const handleDeleteFirst = () => {
49950
- setPendingDelete(true);
49951
- };
49952
- const handleDeleteConfirm = () => {
49953
- for (const id of ids) remove({ deviceId: id });
49954
- setPendingDelete(false);
49955
- onClear();
49956
- };
49957
- const handleDeleteCancel = () => {
49958
- setPendingDelete(false);
49959
- };
49960
- const handleClear = () => {
49961
- setPendingDelete(false);
49962
- onClear();
49963
- };
50729
+ function FanInlineControl({ trpc, deviceId, features, optimistic }) {
50730
+ if (!useFanCapGuard(trpc, deviceId)) return null;
49964
50731
  return /* @__PURE__ */ jsxs("div", {
49965
- className: "flex items-center gap-2 px-3 py-2 rounded-lg border border-border bg-surface-hover",
49966
- children: [
49967
- /* @__PURE__ */ jsxs("span", {
49968
- className: "text-xs font-medium text-foreground-subtle shrink-0",
49969
- children: [count, " selected"]
49970
- }),
49971
- /* @__PURE__ */ jsx("div", {
49972
- className: "h-3.5 w-px bg-border shrink-0",
49973
- "aria-hidden": "true"
49974
- }),
49975
- /* @__PURE__ */ jsxs("div", {
49976
- className: "flex items-center gap-1.5 flex-wrap",
49977
- children: [
49978
- /* @__PURE__ */ jsx("button", {
49979
- type: "button",
49980
- onClick: handleDisable,
49981
- className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
49982
- children: "Disable"
49983
- }),
49984
- /* @__PURE__ */ jsx("button", {
49985
- type: "button",
49986
- onClick: handleEnable,
49987
- className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
49988
- children: "Enable"
49989
- }),
49990
- pendingDelete ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("button", {
49991
- type: "button",
49992
- onClick: handleDeleteConfirm,
49993
- className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-red-500/50 bg-red-500/10 text-red-400 hover:bg-red-500/20"),
49994
- children: [
49995
- "Confirm delete ",
49996
- count,
49997
- "?"
49998
- ]
49999
- }), /* @__PURE__ */ jsx("button", {
50000
- type: "button",
50001
- onClick: handleDeleteCancel,
50002
- className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
50003
- children: "Cancel"
50004
- })] }) : /* @__PURE__ */ jsx("button", {
50005
- type: "button",
50006
- onClick: handleDeleteFirst,
50007
- className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-red-500/40 bg-red-500/10 text-red-400 hover:bg-red-500/20"),
50008
- children: "Delete"
50009
- }),
50010
- /* @__PURE__ */ jsx("button", {
50011
- type: "button",
50012
- onClick: handleClear,
50013
- className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
50014
- children: "Clear"
50015
- })
50016
- ]
50017
- })
50018
- ]
50732
+ className: "inline-flex items-center gap-2",
50733
+ children: [/* @__PURE__ */ jsx(SwitchPanel, {
50734
+ trpc,
50735
+ deviceId,
50736
+ features,
50737
+ layout: "compact",
50738
+ optimistic
50739
+ }), /* @__PURE__ */ jsx(FanPanel, {
50740
+ trpc,
50741
+ deviceId,
50742
+ features,
50743
+ layout: "compact",
50744
+ optimistic
50745
+ })]
50019
50746
  });
50020
50747
  }
50021
- //#endregion
50022
- //#region src/composites/device-list/batch-actions-bar.tsx
50023
- /**
50024
- * <DeviceBatchActionsBar> — responsive wrapper around <DeviceBatchToolbar>.
50025
- *
50026
- * Surfaces the available bulk actions once devices are selected:
50027
- * - L2+ (`sm` and up): the toolbar renders inline as a row of buttons.
50028
- * - L1 (`< sm`): a compact "Actions · N" trigger opens a popover that
50029
- * hosts the SAME <DeviceBatchToolbar>, so the action logic (inline
50030
- * delete-confirm, clear) is reused verbatim — never forked.
50031
- *
50032
- * Renders nothing while the selection is empty (delegated to the toolbar).
50033
- */
50034
- function DeviceBatchActionsBar(props) {
50035
- const [open, setOpen] = useState(false);
50036
- const rootRef = useRef(null);
50037
- const count = props.selectedIds.size;
50038
- useEffect(() => {
50039
- if (!open) return;
50040
- const onOutside = (e) => {
50041
- const target = e.target instanceof Node ? e.target : null;
50042
- if (target && rootRef.current && !rootRef.current.contains(target)) setOpen(false);
50043
- };
50044
- const onKey = (e) => {
50045
- if (e.key === "Escape") setOpen(false);
50046
- };
50047
- document.addEventListener("mousedown", onOutside);
50048
- document.addEventListener("touchstart", onOutside);
50049
- document.addEventListener("keydown", onKey);
50050
- return () => {
50051
- document.removeEventListener("mousedown", onOutside);
50052
- document.removeEventListener("touchstart", onOutside);
50053
- document.removeEventListener("keydown", onKey);
50054
- };
50055
- }, [open]);
50056
- if (count === 0) return null;
50057
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
50058
- className: "hidden sm:block",
50059
- children: /* @__PURE__ */ jsx(DeviceBatchToolbar, { ...props })
50060
- }), /* @__PURE__ */ jsxs("div", {
50061
- ref: rootRef,
50062
- "data-testid": "batch-actions-compact",
50063
- className: "relative sm:hidden",
50064
- children: [/* @__PURE__ */ jsxs("button", {
50065
- type: "button",
50066
- onClick: () => setOpen((v) => !v),
50067
- "aria-haspopup": "menu",
50068
- "aria-expanded": open,
50069
- className: "inline-flex items-center gap-1.5 h-7 px-2.5 rounded-full border border-primary/40 bg-primary/15 text-[10.5px] font-medium text-primary",
50070
- children: [/* @__PURE__ */ jsxs("span", { children: ["Actions · ", String(count)] }), /* @__PURE__ */ jsx(ChevronDown, { className: `h-3 w-3 transition-transform ${open ? "rotate-180" : ""}` })]
50071
- }), open && /* @__PURE__ */ jsx("div", {
50072
- role: "menu",
50073
- className: "absolute left-0 top-full mt-1 z-30 rounded-lg border border-border bg-surface shadow-md",
50074
- children: /* @__PURE__ */ jsx(DeviceBatchToolbar, { ...props })
50075
- })]
50076
- })] });
50748
+ function useClimateCapGuard(trpc, deviceId) {
50749
+ const { cap } = useDeviceCapability({
50750
+ trpc,
50751
+ deviceId,
50752
+ cap: (d) => d.climateControl,
50753
+ slice: (d) => d.state.climateControl
50754
+ });
50755
+ return cap !== void 0;
50077
50756
  }
50078
- //#endregion
50079
- //#region src/composites/device-meta.ts
50080
- var DEVICE_TYPE_META = {
50081
- [DeviceType.Camera]: {
50082
- icon: Camera,
50083
- label: "Camera"
50084
- },
50085
- [DeviceType.Hub]: {
50086
- icon: Network,
50087
- label: "Hub"
50088
- },
50089
- [DeviceType.Light]: {
50090
- icon: Lightbulb,
50091
- label: "Light"
50092
- },
50093
- [DeviceType.Siren]: {
50094
- icon: Siren,
50095
- label: "Siren"
50096
- },
50097
- [DeviceType.Switch]: {
50098
- icon: ToggleLeft,
50099
- label: "Switch"
50100
- },
50101
- [DeviceType.Sensor]: {
50102
- icon: Activity,
50103
- label: "Sensor"
50104
- },
50105
- [DeviceType.Thermostat]: {
50106
- icon: Thermometer,
50107
- label: "Thermostat"
50108
- },
50109
- [DeviceType.Climate]: {
50110
- icon: AirVent,
50111
- label: "Air conditioner"
50112
- },
50113
- [DeviceType.Button]: {
50114
- icon: CircleDot,
50115
- label: "Button"
50116
- },
50117
- [DeviceType.EventEmitter]: {
50118
- icon: Zap,
50119
- label: "Event Emitter"
50120
- },
50121
- [DeviceType.Update]: {
50122
- icon: RefreshCw,
50123
- label: "Update"
50124
- },
50125
- [DeviceType.Generic]: {
50126
- icon: Box,
50127
- label: "Generic"
50128
- },
50129
- [DeviceType.Notifier]: {
50130
- icon: Bell,
50131
- label: "Notifier"
50132
- },
50133
- [DeviceType.Script]: {
50134
- icon: ScrollText,
50135
- label: "Script"
50136
- },
50137
- [DeviceType.Automation]: {
50138
- icon: Workflow,
50139
- label: "Automation"
50140
- },
50141
- [DeviceType.Lock]: {
50142
- icon: Lock,
50143
- label: "Lock"
50144
- },
50145
- [DeviceType.Cover]: {
50146
- icon: Blinds,
50147
- label: "Cover"
50148
- },
50149
- [DeviceType.Valve]: {
50150
- icon: Pipette,
50151
- label: "Valve"
50152
- },
50153
- [DeviceType.Humidifier]: {
50154
- icon: Waves,
50155
- label: "Humidifier"
50156
- },
50157
- [DeviceType.WaterHeater]: {
50158
- icon: Bath,
50159
- label: "Water heater"
50160
- },
50161
- [DeviceType.Fan]: {
50162
- icon: Fan,
50163
- label: "Fan"
50164
- },
50165
- [DeviceType.MediaPlayer]: {
50166
- icon: MonitorPlay,
50167
- label: "Media player"
50168
- },
50169
- [DeviceType.AlarmPanel]: {
50170
- icon: ShieldAlert,
50171
- label: "Alarm panel"
50172
- },
50173
- [DeviceType.Control]: {
50174
- icon: SlidersHorizontal,
50175
- label: "Control"
50176
- },
50177
- [DeviceType.Presence]: {
50178
- icon: MapPin,
50179
- label: "Presence"
50180
- },
50181
- [DeviceType.Weather]: {
50182
- icon: CloudSun,
50183
- label: "Weather"
50184
- },
50185
- [DeviceType.Vacuum]: {
50186
- icon: Bot,
50187
- label: "Vacuum"
50188
- },
50189
- [DeviceType.LawnMower]: {
50190
- icon: Tractor,
50191
- label: "Lawn mower"
50192
- },
50193
- [DeviceType.Container]: {
50194
- icon: Box,
50195
- label: "Container"
50196
- },
50197
- [DeviceType.Image]: {
50198
- icon: Image,
50199
- label: "Image"
50200
- },
50201
- [DeviceType.PetFeeder]: {
50202
- icon: Bone,
50203
- label: "Pet feeder"
50204
- }
50205
- };
50206
- var DEVICE_ROLE_META = {
50207
- [DeviceRole.Siren]: {
50208
- icon: Siren,
50209
- label: "Siren"
50210
- },
50211
- [DeviceRole.Floodlight]: {
50212
- icon: Lightbulb,
50213
- label: "Floodlight"
50214
- },
50215
- [DeviceRole.Spotlight]: {
50216
- icon: Flashlight,
50217
- label: "Spotlight"
50218
- },
50219
- [DeviceRole.PirSensor]: {
50220
- icon: Radar,
50221
- label: "PIR sensor"
50222
- },
50223
- [DeviceRole.Chime]: {
50224
- icon: Bell,
50225
- label: "Chime"
50226
- },
50227
- [DeviceRole.Autotrack]: {
50228
- icon: Crosshair,
50229
- label: "Auto-track"
50230
- },
50231
- [DeviceRole.Nightvision]: {
50232
- icon: Moon,
50233
- label: "Night vision"
50234
- },
50235
- [DeviceRole.PrivacyMask]: {
50236
- icon: EyeOff,
50237
- label: "Privacy mask"
50238
- },
50239
- [DeviceRole.Doorbell]: {
50240
- icon: DoorClosed,
50241
- label: "Doorbell"
50242
- },
50243
- [DeviceRole.BinaryHelper]: {
50244
- icon: ToggleLeft,
50245
- label: "Binary helper"
50246
- },
50247
- [DeviceRole.MotionSensor]: {
50248
- icon: Footprints,
50249
- label: "Motion sensor"
50250
- },
50251
- [DeviceRole.ContactSensor]: {
50252
- icon: DoorOpen,
50253
- label: "Contact sensor"
50254
- },
50255
- [DeviceRole.LeakSensor]: {
50256
- icon: Droplet,
50257
- label: "Leak sensor"
50258
- },
50259
- [DeviceRole.SmokeSensor]: {
50260
- icon: AlarmSmoke,
50261
- label: "Smoke sensor"
50262
- },
50263
- [DeviceRole.COSensor]: {
50264
- icon: Wind,
50265
- label: "CO sensor"
50266
- },
50267
- [DeviceRole.GasSensor]: {
50268
- icon: Flame,
50269
- label: "Gas sensor"
50270
- },
50271
- [DeviceRole.TamperSensor]: {
50272
- icon: ShieldAlert,
50273
- label: "Tamper sensor"
50274
- },
50275
- [DeviceRole.VibrationSensor]: {
50276
- icon: Vibrate,
50277
- label: "Vibration sensor"
50278
- },
50279
- [DeviceRole.ConnectivitySensor]: {
50280
- icon: Wifi,
50281
- label: "Connectivity"
50282
- },
50283
- [DeviceRole.SoundSensor]: {
50284
- icon: Volume2,
50285
- label: "Sound sensor"
50286
- },
50287
- [DeviceRole.BinarySensor]: {
50288
- icon: ToggleLeft,
50289
- label: "Binary sensor"
50290
- },
50291
- [DeviceRole.TemperatureSensor]: {
50292
- icon: Thermometer,
50293
- label: "Temperature"
50294
- },
50295
- [DeviceRole.HumiditySensor]: {
50296
- icon: Droplets,
50297
- label: "Humidity"
50298
- },
50299
- [DeviceRole.AmbientLightSensor]: {
50300
- icon: Sun,
50301
- label: "Ambient light"
50302
- },
50303
- [DeviceRole.PressureSensor]: {
50304
- icon: Gauge,
50305
- label: "Pressure"
50757
+ function ThermostatInlineControl({ trpc, deviceId, features, optimistic }) {
50758
+ if (!useClimateCapGuard(trpc, deviceId)) return null;
50759
+ return /* @__PURE__ */ jsx(ClimatePanel, {
50760
+ trpc,
50761
+ deviceId,
50762
+ features,
50763
+ layout: "compact",
50764
+ optimistic
50765
+ });
50766
+ }
50767
+ function AcInlineControl({ trpc, deviceId, features, optimistic }) {
50768
+ if (!useClimateCapGuard(trpc, deviceId)) return null;
50769
+ return /* @__PURE__ */ jsx(AcPanel, {
50770
+ trpc,
50771
+ deviceId,
50772
+ features,
50773
+ layout: "compact",
50774
+ optimistic
50775
+ });
50776
+ }
50777
+ function AcHeroCard({ trpc, deviceId, features, optimistic }) {
50778
+ if (!useClimateCapGuard(trpc, deviceId)) return null;
50779
+ return /* @__PURE__ */ jsx(AcPanel, {
50780
+ trpc,
50781
+ deviceId,
50782
+ features,
50783
+ layout: "full",
50784
+ optimistic
50785
+ });
50786
+ }
50787
+ function useMediaPlayerCapGuard(trpc, deviceId) {
50788
+ const { cap } = useDeviceCapability({
50789
+ trpc,
50790
+ deviceId,
50791
+ cap: (d) => d.mediaPlayer,
50792
+ slice: (d) => d.state.mediaPlayer
50793
+ });
50794
+ return cap !== void 0;
50795
+ }
50796
+ function MediaPlayerInlineControl({ trpc, deviceId, features, optimistic }) {
50797
+ if (!useMediaPlayerCapGuard(trpc, deviceId)) return null;
50798
+ return /* @__PURE__ */ jsx(MediaPlayerPanel, {
50799
+ trpc,
50800
+ deviceId,
50801
+ features,
50802
+ layout: "compact",
50803
+ optimistic
50804
+ });
50805
+ }
50806
+ function useControlCapGuard(trpc, deviceId) {
50807
+ const { cap } = useDeviceCapability({
50808
+ trpc,
50809
+ deviceId,
50810
+ cap: (d) => d.control,
50811
+ slice: (d) => d.state.control
50812
+ });
50813
+ return cap !== void 0;
50814
+ }
50815
+ function ControlInlineControl({ trpc, deviceId, features, optimistic }) {
50816
+ if (!useControlCapGuard(trpc, deviceId)) return null;
50817
+ return /* @__PURE__ */ jsx(ControlPanel, {
50818
+ trpc,
50819
+ deviceId,
50820
+ features,
50821
+ layout: "compact",
50822
+ optimistic
50823
+ });
50824
+ }
50825
+ function ControlHeroCard({ trpc, deviceId, features, optimistic }) {
50826
+ if (!useControlCapGuard(trpc, deviceId)) return null;
50827
+ return /* @__PURE__ */ jsx(HeroShell, { children: /* @__PURE__ */ jsx(ControlPanel, {
50828
+ trpc,
50829
+ deviceId,
50830
+ features,
50831
+ layout: "full",
50832
+ optimistic
50833
+ }) });
50834
+ }
50835
+ //#endregion
50836
+ //#region src/composites/device-controls/control-registry.tsx
50837
+ /**
50838
+ * Presentation binding: the shared `DeviceControlKind` (decided in
50839
+ * `@camstack/types` `resolveDeviceControlKind`) → this platform's web
50840
+ * components. Exhaustive over `DeviceControlKind` — omitting a kind fails the
50841
+ * build here. The type→kind DECISION is single-sourced in `@camstack/types`;
50842
+ * this map only binds a kind to its web widgets.
50843
+ */
50844
+ var CONTROL_KIND_COMPONENTS = {
50845
+ cover: {
50846
+ InlineControl: CoverInlineControl,
50847
+ HeroCard: CoverHeroCard
50306
50848
  },
50307
- [DeviceRole.WindSensor]: {
50308
- icon: Wind,
50309
- label: "Wind"
50849
+ valve: {
50850
+ InlineControl: ValveInlineControl,
50851
+ HeroCard: ValveHeroCard
50310
50852
  },
50311
- [DeviceRole.RainSensor]: {
50312
- icon: CloudRain,
50313
- label: "Rain"
50853
+ humidifier: {
50854
+ InlineControl: HumidifierInlineControl,
50855
+ HeroCard: HumidifierHeroCard
50314
50856
  },
50315
- [DeviceRole.UvSensor]: {
50316
- icon: SunMedium,
50317
- label: "UV index"
50857
+ "water-heater": {
50858
+ InlineControl: WaterHeaterInlineControl,
50859
+ HeroCard: WaterHeaterHeroCard
50318
50860
  },
50319
- [DeviceRole.SolarRadiationSensor]: {
50320
- icon: Sunrise,
50321
- label: "Solar radiation"
50861
+ switch: {
50862
+ InlineControl: SwitchInlineControl,
50863
+ HeroCard: SwitchHeroCard
50322
50864
  },
50323
- [DeviceRole.SoilMoistureSensor]: {
50324
- icon: Sprout,
50325
- label: "Soil moisture"
50865
+ light: {
50866
+ InlineControl: LightInlineControl,
50867
+ HeroCard: LightHeroCard
50326
50868
  },
50327
- [DeviceRole.PowerSensor]: {
50328
- icon: Zap,
50329
- label: "Power"
50869
+ fan: {
50870
+ InlineControl: FanInlineControl,
50871
+ HeroCard: FanHeroCard
50330
50872
  },
50331
- [DeviceRole.EnergySensor]: {
50332
- icon: Zap,
50333
- label: "Energy"
50873
+ sensor: {
50874
+ InlineControl: SensorInlineControl,
50875
+ HeroCard: SensorHeroCard
50334
50876
  },
50335
- [DeviceRole.VoltageSensor]: {
50336
- icon: Zap,
50337
- label: "Voltage"
50877
+ thermostat: {
50878
+ InlineControl: ThermostatInlineControl,
50879
+ HeroCard: ThermostatHeroCard
50338
50880
  },
50339
- [DeviceRole.CurrentSensor]: {
50340
- icon: Zap,
50341
- label: "Current"
50881
+ climate: {
50882
+ InlineControl: AcInlineControl,
50883
+ HeroCard: AcHeroCard
50342
50884
  },
50343
- [DeviceRole.AirQualitySensor]: {
50344
- icon: Wind,
50345
- label: "Air quality"
50885
+ button: {
50886
+ InlineControl: ButtonControl,
50887
+ HeroCard: ButtonHeroCard
50346
50888
  },
50347
- [DeviceRole.BatterySensor]: {
50348
- icon: Gauge,
50349
- label: "Battery"
50889
+ update: {
50890
+ InlineControl: UpdateInlineControl,
50891
+ HeroCard: UpdateHeroCard
50350
50892
  },
50351
- [DeviceRole.NumericSensor]: {
50352
- icon: Hash,
50353
- label: "Numeric sensor"
50893
+ lock: {
50894
+ InlineControl: LockInlineControl,
50895
+ HeroCard: LockHeroCard
50354
50896
  },
50355
- [DeviceRole.EnumSensor]: {
50356
- icon: List,
50357
- label: "Enum sensor"
50897
+ "media-player": {
50898
+ InlineControl: MediaPlayerInlineControl,
50899
+ HeroCard: MediaPlayerHeroCard
50358
50900
  },
50359
- [DeviceRole.DateTimeSensor]: {
50360
- icon: Clock,
50361
- label: "Date / time"
50901
+ alarm: {
50902
+ InlineControl: AlarmInlineControl,
50903
+ HeroCard: AlarmHeroCard
50362
50904
  },
50363
- [DeviceRole.GenericSensor]: {
50364
- icon: Gauge,
50365
- label: "Sensor"
50905
+ control: {
50906
+ InlineControl: ControlInlineControl,
50907
+ HeroCard: ControlHeroCard
50366
50908
  },
50367
- [DeviceRole.NumericControl]: {
50368
- icon: SlidersHorizontal,
50369
- label: "Numeric control"
50909
+ weather: {
50910
+ InlineControl: WeatherInlineControl,
50911
+ HeroCard: WeatherHeroCard
50370
50912
  },
50371
- [DeviceRole.SelectControl]: {
50372
- icon: ListChecks,
50373
- label: "Select control"
50913
+ vacuum: {
50914
+ InlineControl: VacuumInlineControl,
50915
+ HeroCard: VacuumHeroCard
50374
50916
  },
50375
- [DeviceRole.TextControl]: {
50376
- icon: Type,
50377
- label: "Text control"
50917
+ "lawn-mower": {
50918
+ InlineControl: LawnMowerInlineControl,
50919
+ HeroCard: LawnMowerHeroCard
50378
50920
  },
50379
- [DeviceRole.DateTimeControl]: {
50380
- icon: Clock,
50381
- label: "Date / time control"
50921
+ image: {
50922
+ InlineControl: ImageInlineControl,
50923
+ HeroCard: ImageHeroCard
50382
50924
  },
50383
- [DeviceRole.MobilePushNotifier]: {
50384
- icon: Smartphone,
50385
- label: "Mobile push"
50925
+ "pet-feeder": {
50926
+ InlineControl: PetFeederInlineControl,
50927
+ HeroCard: PetFeederHeroCard
50386
50928
  },
50387
- [DeviceRole.MessagingNotifier]: {
50388
- icon: MessageSquare,
50389
- label: "Messaging"
50929
+ script: {
50930
+ InlineControl: ScriptInlineControl,
50931
+ HeroCard: ScriptHeroCard
50390
50932
  },
50391
- [DeviceRole.EmailNotifier]: {
50392
- icon: Mail,
50393
- label: "Email"
50933
+ automation: {
50934
+ InlineControl: AutomationInlineControl,
50935
+ HeroCard: AutomationHeroCard
50394
50936
  },
50395
- [DeviceRole.GenericNotifier]: {
50396
- icon: Bell,
50397
- label: "Notifier"
50937
+ dummy: {
50938
+ InlineControl: DummyInline,
50939
+ HeroCard: DummyHeroCard
50398
50940
  }
50399
50941
  };
50942
+ /**
50943
+ * Exhaustive record over DeviceType, DERIVED from the shared decision
50944
+ * (`DEVICE_TYPE_CONTROL_KIND`) composed with the web `CONTROL_KIND_COMPONENTS`
50945
+ * binding — so the type→control mapping can never diverge from the single
50946
+ * source in `@camstack/types`. Preserved as a public export for consumers/tests
50947
+ * that index it by `DeviceType`.
50948
+ *
50949
+ * The `Object.fromEntries` result is a `{ [k: string]: DeviceTypeControl }`;
50950
+ * `Object.values(DeviceType)` (a string enum) yields exactly the enum's members,
50951
+ * so the record is fully populated — the assertion narrows that documented
50952
+ * enum-record projection boundary.
50953
+ */
50954
+ var DEVICE_TYPE_CONTROL = Object.fromEntries(Object.values(DeviceType).map((type) => [type, CONTROL_KIND_COMPONENTS[DEVICE_TYPE_CONTROL_KIND[type]]]));
50955
+ /**
50956
+ * Per-type FILL flag.
50957
+ *
50958
+ * true — range / choice / button controls (cover ▲■▼ triad, thermostat
50959
+ * setpoint stepper, media/control/button action buttons, alarm
50960
+ * segmented control). The child STRETCHES to the full column width.
50961
+ * false — readouts, tap-toggles, icon controls (lock), and dummy/placeholder
50962
+ * types. The child keeps its NATURAL width; where it sits in the
50963
+ * column (flush right vs centered) is decided by the caller via a
50964
+ * `ControlPlacement` — see `resolveControlAlign`.
50965
+ *
50966
+ * Dummy types (Camera, Hub, Generic, Weather, Vacuum, Container, and placeholder
50967
+ * types Notifier/Script/Automation whose InlineControl renders "—") are `false` —
50968
+ * the dash placeholder must not be stretched to the full column width. (AlarmPanel
50969
+ * has a real segmented inline control and is `true`.) When a later task gives a
50970
+ * remaining placeholder type a real stretchy inline control, flip its flag to true.
50971
+ *
50972
+ * Exhaustive over DeviceType — a missing member is a TypeScript build error.
50973
+ */
50974
+ var CONTROL_FILLS = {
50975
+ [DeviceType.Cover]: true,
50976
+ [DeviceType.Valve]: true,
50977
+ [DeviceType.Thermostat]: true,
50978
+ [DeviceType.Climate]: true,
50979
+ [DeviceType.Control]: true,
50980
+ [DeviceType.Button]: true,
50981
+ [DeviceType.MediaPlayer]: true,
50982
+ [DeviceType.AlarmPanel]: true,
50983
+ [DeviceType.Lock]: false,
50984
+ [DeviceType.Vacuum]: false,
50985
+ [DeviceType.LawnMower]: false,
50986
+ [DeviceType.Humidifier]: false,
50987
+ [DeviceType.Switch]: false,
50988
+ [DeviceType.Siren]: false,
50989
+ [DeviceType.Light]: false,
50990
+ [DeviceType.Fan]: false,
50991
+ [DeviceType.WaterHeater]: false,
50992
+ [DeviceType.Sensor]: false,
50993
+ [DeviceType.Presence]: false,
50994
+ [DeviceType.EventEmitter]: false,
50995
+ [DeviceType.Notifier]: false,
50996
+ [DeviceType.Script]: false,
50997
+ [DeviceType.Automation]: false,
50998
+ [DeviceType.Camera]: false,
50999
+ [DeviceType.Hub]: false,
51000
+ [DeviceType.Update]: false,
51001
+ [DeviceType.Generic]: false,
51002
+ [DeviceType.Weather]: false,
51003
+ [DeviceType.Container]: false,
51004
+ [DeviceType.Image]: false,
51005
+ [DeviceType.PetFeeder]: false
51006
+ };
50400
51007
  var DEVICE_TYPE_VALUES = new Set(Object.values(DeviceType));
50401
- var DEVICE_ROLE_VALUES = new Set(Object.values(DeviceRole));
50402
- function isDeviceType(value) {
50403
- return DEVICE_TYPE_VALUES.has(value);
50404
- }
50405
- function isDeviceRole(value) {
50406
- return DEVICE_ROLE_VALUES.has(value);
50407
- }
50408
- /** Meta for a known `DeviceType`. */
50409
- function deviceTypeMeta(type) {
50410
- return DEVICE_TYPE_META[type];
50411
- }
50412
- /** Meta for a known `DeviceRole`. */
50413
- function deviceRoleMeta(role) {
50414
- return DEVICE_ROLE_META[role];
51008
+ /**
51009
+ * Runtime-string-safe lookup: a device row's `type` crosses the wire as a plain
51010
+ * string. The type→control-kind DECISION is single-sourced in `@camstack/types`
51011
+ * (`resolveDeviceControlKind`, null for an unrecognised type); this maps the
51012
+ * resolved kind onto the web components.
51013
+ */
51014
+ function resolveDeviceControl(type) {
51015
+ const kind = resolveDeviceControlKind(type);
51016
+ return kind ? CONTROL_KIND_COMPONENTS[kind] : null;
50415
51017
  }
50416
- var FALLBACK_DEVICE_TYPE_META = DEVICE_TYPE_META[DeviceType.Generic];
50417
- var FALLBACK_DEVICE_ROLE_META = DEVICE_ROLE_META[DeviceRole.GenericSensor];
50418
51018
  /**
50419
- * Runtime-string-safe lookup: device data crosses process / network boundaries
50420
- * as plain strings, so an unrecognised value falls back to the `Generic` meta
50421
- * instead of throwing.
51019
+ * Runtime-string-safe alignment lookup for the fixed-width control column.
50422
51020
  *
50423
- * This also guards mixed-version module-federation loads where the host
50424
- * `@camstack/types` enum has a new member before the loaded ui-library bundle
50425
- * has the matching `DEVICE_TYPE_META` entry.
51021
+ * A device's `type` crosses the wire as a plain string;
51022
+ * `DEVICE_TYPE_VALUES.has(type)` narrows it to `DeviceType` before indexing
51023
+ * `CONTROL_FILLS`, making the cast sound. The `'table'` placement is
51024
+ * content-width for EVERY type (returns `FIT`), so a wide control never
51025
+ * overflows a fixed box — the FILL flag is irrelevant there. For the fixed-box
51026
+ * placements a FILL type always stretches (`FILL`) and a natural-width type is
51027
+ * placed by the caller's `placement` — `'center'` → `CENTER`, `'right'` →
51028
+ * `RIGHT` (card / minimal view). An unrecognised type falls back to
51029
+ * natural-width placement.
50426
51030
  */
50427
- function deviceTypeMetaOf(type) {
50428
- if (!isDeviceType(type)) return FALLBACK_DEVICE_TYPE_META;
50429
- return DEVICE_TYPE_META[type] ?? FALLBACK_DEVICE_TYPE_META;
50430
- }
50431
- /** Runtime-string-safe role lookup; unknown roles fall back to `GenericSensor`. */
50432
- function deviceRoleMetaOf(role) {
50433
- if (!isDeviceRole(role)) return FALLBACK_DEVICE_ROLE_META;
50434
- return DEVICE_ROLE_META[role] ?? FALLBACK_DEVICE_ROLE_META;
51031
+ function resolveControlAlign(type, placement) {
51032
+ if (placement === "table") return "fit";
51033
+ return DEVICE_TYPE_VALUES.has(type) && CONTROL_FILLS[type] ? FILL : placement === "center" ? CENTER : RIGHT;
50435
51034
  }
51035
+ //#endregion
51036
+ //#region src/lib/format-last-seen.ts
50436
51037
  /**
50437
- * Icons an operator may pick for a per-device `display.icon` override (and for
50438
- * a per-role default), keyed by a stable kebab-case name. Named imports only —
50439
- * no dynamic indexing into lucide, so the bundle carries exactly this set.
50440
- * Unknown keys resolve to `undefined` (caller falls through to the next level).
51038
+ * Relative "last seen" formatter for the offline affordance.
50441
51039
  *
50442
- * The vocabulary is the one the VIEWER draws from too (its own icon module,
50443
- * keyed by these same strings): a key added here that the phone cannot draw
50444
- * falls through to the next resolution level there, which reads as "my change
50445
- * did nothing". So the KEY is the contract — a stable kebab-case string, never
50446
- * a glyph identity — and `device-meta.test.ts` pins that every key resolves
50447
- * and that the set never loses a key an operator may already have persisted.
51040
+ * Renders a short relative-time label from a millisecond epoch:
51041
+ * "just now", "5m ago", "3h ago", "2d ago". Returns `null` for missing
51042
+ * inputs, or inputs below {@link MIN_PLAUSIBLE_TIMESTAMP_MS} (a
51043
+ * placeholder or a seeded "known value, unknown change time" stamp), so
51044
+ * callers can render nothing rather than a misleading "—", "NaN ago", or
51045
+ * (for a seeded `1`) "20000 days ago".
50448
51046
  *
50449
- * Grouped by what an operator is naming: a device, a light or appliance, a
50450
- * sensor or the weather it reads, a room, or what moves through it. `gauge2`
50451
- * is a legacy alias kept for rows that persisted it.
51047
+ * Future timestamps (clock skew between the device and the browser)
51048
+ * collapse to "just now" instead of a negative duration.
50452
51049
  */
50453
- var DISPLAY_ICON_REGISTRY = {
50454
- camera: Camera,
50455
- network: Network,
50456
- "toggle-left": ToggleLeft,
50457
- "toggle-right": ToggleRight,
50458
- "circle-dot": CircleDot,
50459
- "refresh-cw": RefreshCw,
50460
- box: Box,
50461
- "scroll-text": ScrollText,
50462
- workflow: Workflow,
50463
- lock: Lock,
50464
- "lock-open": LockOpen,
50465
- blinds: Blinds,
50466
- "air-vent": AirVent,
50467
- "monitor-play": MonitorPlay,
50468
- "sliders-horizontal": SlidersHorizontal,
50469
- "map-pin": MapPin,
50470
- bot: Bot,
50471
- tractor: Tractor,
50472
- image: Image,
50473
- bone: Bone,
50474
- "door-closed": DoorClosed,
50475
- "door-open": DoorOpen,
50476
- crosshair: Crosshair,
50477
- eye: Eye,
50478
- "eye-off": EyeOff,
50479
- smartphone: Smartphone,
50480
- phone: Phone,
50481
- tablet: Tablet,
50482
- laptop: Laptop,
50483
- monitor: Monitor,
50484
- tv: Tv,
50485
- speaker: Speaker,
50486
- printer: Printer,
50487
- projector: Projector,
50488
- "gamepad-2": Gamepad2,
50489
- radio: Radio,
50490
- router: Router,
50491
- bluetooth: Bluetooth,
50492
- cpu: Cpu,
50493
- "hard-drive": HardDrive,
50494
- server: Server,
50495
- mic: Mic,
50496
- "mic-off": MicOff,
50497
- video: Video,
50498
- "video-off": VideoOff,
50499
- music: Music,
50500
- plug: Plug,
50501
- "plug-zap": PlugZap,
50502
- power: Power,
50503
- "power-off": PowerOff,
50504
- cable: Cable,
50505
- battery: Battery,
50506
- "battery-charging": BatteryCharging,
50507
- "battery-full": BatteryFull,
50508
- "battery-low": BatteryLow,
50509
- key: Key,
50510
- "key-round": KeyRound,
50511
- mailbox: Mailbox,
50512
- package: Package,
50513
- shield: Shield,
50514
- "shield-check": ShieldCheck,
50515
- "shield-off": ShieldOff,
50516
- lightbulb: Lightbulb,
50517
- lamp: Lamp,
50518
- "lamp-ceiling": LampCeiling,
50519
- "lamp-desk": LampDesk,
50520
- "lamp-floor": LampFloor,
50521
- flashlight: Flashlight,
50522
- heater: Heater,
50523
- snowflake: Snowflake,
50524
- "thermometer-sun": ThermometerSun,
50525
- "thermometer-snowflake": ThermometerSnowflake,
50526
- refrigerator: Refrigerator,
50527
- microwave: Microwave,
50528
- "washing-machine": WashingMachine,
50529
- "cooking-pot": CookingPot,
50530
- utensils: Utensils,
50531
- thermometer: Thermometer,
50532
- droplets: Droplets,
50533
- droplet: Droplet,
50534
- gauge: Gauge,
50535
- gauge2: Gauge,
50536
- sun: Sun,
50537
- "sun-medium": SunMedium,
50538
- sunrise: Sunrise,
50539
- moon: Moon,
50540
- wind: Wind,
50541
- cloud: Cloud,
50542
- "cloud-rain": CloudRain,
50543
- "cloud-sun": CloudSun,
50544
- waves: Waves,
50545
- sprout: Sprout,
50546
- leaf: Leaf,
50547
- flower: Flower,
50548
- zap: Zap,
50549
- flame: Flame,
50550
- bell: Bell,
50551
- "bell-ring": BellRing,
50552
- "bell-off": BellOff,
50553
- siren: Siren,
50554
- radar: Radar,
50555
- activity: Activity,
50556
- hash: Hash,
50557
- clock: Clock,
50558
- timer: Timer,
50559
- calendar: Calendar,
50560
- pipette: Pipette,
50561
- fan: Fan,
50562
- bath: Bath,
50563
- footprints: Footprints,
50564
- "alarm-smoke": AlarmSmoke,
50565
- "shield-alert": ShieldAlert,
50566
- vibrate: Vibrate,
50567
- wifi: Wifi,
50568
- "wifi-off": WifiOff,
50569
- "volume-2": Volume2,
50570
- "volume-x": VolumeX,
50571
- heart: Heart,
50572
- "heart-pulse": HeartPulse,
50573
- star: Star,
50574
- sparkles: Sparkles,
50575
- house: House,
50576
- sofa: Sofa,
50577
- "bed-double": BedDouble,
50578
- bed: Bed,
50579
- warehouse: Warehouse,
50580
- trees: Trees,
50581
- "tree-pine": TreePine,
50582
- fence: Fence,
50583
- store: Store,
50584
- building: Building,
50585
- "building-2": Building2,
50586
- mountain: Mountain,
50587
- umbrella: Umbrella,
50588
- dumbbell: Dumbbell,
50589
- wrench: Wrench,
50590
- hammer: Hammer,
50591
- drill: Drill,
50592
- "waves-ladder": WavesLadder,
50593
- "circle-parking": CircleParking,
50594
- briefcase: Briefcase,
50595
- route: Route,
50596
- car: Car,
50597
- bike: Bike,
50598
- bus: Bus,
50599
- truck: Truck,
50600
- plane: Plane,
50601
- "train-front": TrainFront,
50602
- sailboat: Sailboat,
50603
- dog: Dog,
50604
- cat: Cat,
50605
- bird: Bird,
50606
- "paw-print": PawPrint,
50607
- baby: Baby,
50608
- user: User,
50609
- users: Users,
50610
- "person-standing": PersonStanding
50611
- };
50612
- /** Resolve a `display.icon` name to a lucide component, or `undefined` when the
50613
- * name is not in the curated `DISPLAY_ICON_REGISTRY`. */
50614
- function resolveDisplayIcon(name) {
50615
- return Object.hasOwn(DISPLAY_ICON_REGISTRY, name) ? DISPLAY_ICON_REGISTRY[name] : void 0;
51050
+ var SECOND_MS = 1e3;
51051
+ var MINUTE_MS = 60 * SECOND_MS;
51052
+ var HOUR_MS = 60 * MINUTE_MS;
51053
+ var DAY_MS = 24 * HOUR_MS;
51054
+ var JUST_NOW_MS = 30 * SECOND_MS;
51055
+ /**
51056
+ * The earliest timestamp a real "last seen" could plausibly carry. Below
51057
+ * this, a value is a placeholder or a seeded "known value, unknown change
51058
+ * time" stamp (e.g. Homematic's `HM_SEEDED_CHANGED_AT = 1`) — never a real
51059
+ * instant. Year 2000 predates every device this renders.
51060
+ */
51061
+ var MIN_PLAUSIBLE_TIMESTAMP_MS = Date.UTC(2e3, 0, 1);
51062
+ function formatLastSeen(timestampMs) {
51063
+ if (timestampMs === null || timestampMs === void 0 || timestampMs < MIN_PLAUSIBLE_TIMESTAMP_MS) return null;
51064
+ const delta = Date.now() - timestampMs;
51065
+ if (delta < JUST_NOW_MS) return "just now";
51066
+ if (delta < HOUR_MS) return `${String(Math.floor(delta / MINUTE_MS))}m ago`;
51067
+ if (delta < DAY_MS) return `${String(Math.floor(delta / HOUR_MS))}h ago`;
51068
+ return `${String(Math.floor(delta / DAY_MS))}d ago`;
51069
+ }
51070
+ //#endregion
51071
+ //#region src/composites/device-controls/offline-badge.tsx
51072
+ function OfflineBadge({ lastChangedAt, className }) {
51073
+ const lastSeen = formatLastSeen(lastChangedAt);
51074
+ return /* @__PURE__ */ jsxs("span", {
51075
+ title: lastSeen === null ? "Unavailable" : `Unavailable · last seen ${lastSeen}`,
51076
+ className: cn("inline-flex items-center gap-1 rounded-full px-2 py-0.5", "bg-surface-hover text-foreground-subtle text-[10px] font-medium", "border border-border", className),
51077
+ children: [
51078
+ /* @__PURE__ */ jsx(WifiOff, {
51079
+ className: "h-3 w-3 flex-shrink-0",
51080
+ "aria-hidden": "true"
51081
+ }),
51082
+ /* @__PURE__ */ jsx("span", { children: "Unavailable" }),
51083
+ lastSeen !== null ? /* @__PURE__ */ jsx("span", {
51084
+ className: "text-foreground-subtle/70",
51085
+ children: `· last seen ${lastSeen}`
51086
+ }) : null
51087
+ ]
51088
+ });
50616
51089
  }
51090
+ //#endregion
51091
+ //#region src/composites/device-controls/primary-child.ts
50617
51092
  /**
50618
- * EXHAUSTIVE device-type filter options — one entry per `DeviceType` member,
50619
- * derived from `DEVICE_TYPE_META` so the set is always complete and a newly
50620
- * added `DeviceType` automatically appears in every type filter (Devices page,
50621
- * adoption modal, …) without touching any consumer.
51093
+ * Which child a container is rendered AS — this surface's name for the shared
51094
+ * rule.
50622
51095
  *
50623
- * The previous per-consumer logic built options from the types PRESENT in the
50624
- * list (or a hand-maintained subset), so recently-added types (vacuum, lawn
50625
- * mower, valve, humidifier, water heater, weather, image, …) were silently
50626
- * missing from the dropdown. This is the single source of truth instead.
51096
+ * The list and the resolution used to live here, in a COPY that carried a
51097
+ * comment asking the Home Assistant adoption mapping and the viewer's panel to
51098
+ * stay in sync with it. Three lists and three comments is not one rule, it is
51099
+ * three chances to drift. Both now come from `@camstack/types`, which all of
51100
+ * them already depend on; what remains here is the adapter for this module's
51101
+ * historical `ChildRef` shape.
51102
+ */
51103
+ /** The shared precedence, under this module's historical name. */
51104
+ var PRIORITY = CONTAINER_CHILD_PRIORITY;
51105
+ /**
51106
+ * Resolve a container's primary child: the operator's pick when it still
51107
+ * matches a live child, else the highest-priority type.
50627
51108
  *
50628
- * `id` is the raw `DeviceType` string (matches the filtering predicate, which
50629
- * compares against `device.type`); `label` + `icon` come from the meta map.
50630
- * Ordered by `DeviceType` declaration order for a stable, intentional layout.
51109
+ * `entityId` here is `sourceInfo?.id ?? stableId` (see `containerChildToRef`).
51110
+ * The shared resolver matches its canonical `stableId` key first and accepts
51111
+ * that legacy value too, so a pick stored under either keeps resolving.
50631
51112
  */
50632
- var ALL_DEVICE_TYPE_FILTER_OPTIONS = Object.values(DeviceType).map((type) => {
50633
- const meta = deviceTypeMetaOf(type);
51113
+ function resolvePrimaryChild(children, overrideEntityId, containerName) {
51114
+ const picked = resolveContainerPrimaryChild(children.map((c) => ({
51115
+ id: c.id,
51116
+ stableId: c.entityId,
51117
+ type: c.type,
51118
+ ...c.role !== void 0 ? { role: c.role } : {},
51119
+ ...c.name !== void 0 ? { name: c.name } : {}
51120
+ })), overrideEntityId, containerName);
51121
+ return picked === null ? null : children.find((c) => c.id === picked.id) ?? null;
51122
+ }
51123
+ //#endregion
51124
+ //#region src/composites/device-controls/container-primary.ts
51125
+ /**
51126
+ * Re-sync-stable identity of a child. Prefers the upstream-system dispatch key
51127
+ * (`sourceInfo.id` — e.g. HA `entity_id`); falls back to `stableId` for
51128
+ * providers that never populated SourceInfo. Both survive a re-sync that may
51129
+ * reallocate the numeric `id`.
51130
+ */
51131
+ function childEntityId(child) {
51132
+ return child.sourceInfo?.id ?? child.stableId;
51133
+ }
51134
+ /** Project a raw child row onto the pure resolver's {@link ChildRef} shape. */
51135
+ function containerChildToRef(child) {
50634
51136
  return {
50635
- id: type,
50636
- label: meta.label,
50637
- icon: meta.icon
51137
+ id: child.id,
51138
+ entityId: childEntityId(child),
51139
+ type: child.type,
51140
+ ...child.role !== void 0 && child.role !== null ? { role: child.role } : {},
51141
+ ...child.name !== void 0 ? { name: child.name } : {}
50638
51142
  };
50639
- });
50640
- function allDeviceTypeFilterOptions() {
50641
- return ALL_DEVICE_TYPE_FILTER_OPTIONS;
51143
+ }
51144
+ /**
51145
+ * Resolve the entityId override from the container's persisted `linkDeviceId`.
51146
+ * Returns the linked child's stable entityId, or `undefined` when there is no
51147
+ * link or the link is stale (points to a numeric id no longer present) — in
51148
+ * which case the resolver falls back to the priority default.
51149
+ */
51150
+ function overrideEntityIdFromLink(children, linkDeviceId) {
51151
+ if (linkDeviceId === null || linkDeviceId === void 0) return void 0;
51152
+ const linked = children.find((c) => c.id === linkDeviceId);
51153
+ return linked === void 0 ? void 0 : childEntityId(linked);
51154
+ }
51155
+ /**
51156
+ * Full container → primary-child resolution: project children to refs, resolve
51157
+ * the override entityId, and delegate to the pure resolver. Returns `null` for a
51158
+ * container with no children (caller degrades gracefully).
51159
+ *
51160
+ * Override precedence:
51161
+ * 1. `primaryChildEntityId` — the DURABLE override (re-sync/rename-stable),
51162
+ * passed straight to the resolver which matches on `entityId`. Wins when a
51163
+ * non-empty string is supplied.
51164
+ * 2. `linkDeviceId` — LEGACY numeric override, re-derived to an entityId from
51165
+ * the live child set. Consulted only when there is no durable override.
51166
+ *
51167
+ * When neither resolves to a live child, the resolver falls back to the
51168
+ * priority default.
51169
+ */
51170
+ function resolveContainerPrimary(children, linkDeviceId, primaryChildEntityId) {
51171
+ return resolvePrimaryChild(children.map(containerChildToRef), primaryChildEntityId !== null && primaryChildEntityId !== void 0 && primaryChildEntityId !== "" ? primaryChildEntityId : overrideEntityIdFromLink(children, linkDeviceId));
51172
+ }
51173
+ //#endregion
51174
+ //#region src/composites/device-controls/container-primary-hero.tsx
51175
+ function ContainerPrimaryHero({ trpc, children, linkDeviceId, primaryChildEntityId }) {
51176
+ const primary = resolveContainerPrimary(children, linkDeviceId, primaryChildEntityId);
51177
+ const primaryChild = primary === null ? void 0 : children.find((c) => c.id === primary.id);
51178
+ if (primaryChild === void 0) return /* @__PURE__ */ jsx("div", {
51179
+ className: "flex h-full w-full items-center justify-center p-4 text-[11px] italic text-foreground-subtle",
51180
+ children: "No child entities to display."
51181
+ });
51182
+ return /* @__PURE__ */ jsx("div", {
51183
+ className: "flex h-full w-full flex-col items-stretch gap-3 p-3 sm:p-4",
51184
+ children: /* @__PURE__ */ jsx(resolveDeviceControl(primaryChild.type)?.HeroCard ?? DummyHeroCard, {
51185
+ trpc,
51186
+ deviceId: primaryChild.id,
51187
+ features: primaryChild.features
51188
+ })
51189
+ });
51190
+ }
51191
+ //#endregion
51192
+ //#region src/composites/device-controls/primary-child-picker.tsx
51193
+ var BASE_SELECT_CLASS = "rounded-md border border-border bg-surface px-1.5 py-0.5 text-[11px] text-foreground";
51194
+ function PrimaryChildPicker({ childrenList, selectedId, onSetPrimary, disabled, className }) {
51195
+ return /* @__PURE__ */ jsx("select", {
51196
+ value: selectedId,
51197
+ disabled,
51198
+ onChange: (e) => {
51199
+ const nextId = Number(e.target.value);
51200
+ if (!Number.isFinite(nextId) || nextId === selectedId) return;
51201
+ const picked = childrenList.find((c) => c.id === nextId);
51202
+ if (picked !== void 0) onSetPrimary(picked.stableId);
51203
+ },
51204
+ className: className === void 0 ? BASE_SELECT_CLASS : `${BASE_SELECT_CLASS} ${className}`,
51205
+ children: childrenList.map((c) => /* @__PURE__ */ jsxs("option", {
51206
+ value: c.id,
51207
+ children: [
51208
+ c.name,
51209
+ " (",
51210
+ c.type,
51211
+ ")"
51212
+ ]
51213
+ }, c.id))
51214
+ });
51215
+ }
51216
+ //#endregion
51217
+ //#region src/composites/device-list/batch-toolbar.tsx
51218
+ function DeviceBatchToolbar({ selectedIds, onClear, remove, disable, enable }) {
51219
+ const [pendingDelete, setPendingDelete] = useState(false);
51220
+ if (selectedIds.size === 0) return null;
51221
+ const ids = [...selectedIds];
51222
+ const count = ids.length;
51223
+ const handleDisable = () => {
51224
+ for (const id of ids) disable({ deviceId: id });
51225
+ onClear();
51226
+ };
51227
+ const handleEnable = () => {
51228
+ for (const id of ids) enable({ deviceId: id });
51229
+ onClear();
51230
+ };
51231
+ const handleDeleteFirst = () => {
51232
+ setPendingDelete(true);
51233
+ };
51234
+ const handleDeleteConfirm = () => {
51235
+ for (const id of ids) remove({ deviceId: id });
51236
+ setPendingDelete(false);
51237
+ onClear();
51238
+ };
51239
+ const handleDeleteCancel = () => {
51240
+ setPendingDelete(false);
51241
+ };
51242
+ const handleClear = () => {
51243
+ setPendingDelete(false);
51244
+ onClear();
51245
+ };
51246
+ return /* @__PURE__ */ jsxs("div", {
51247
+ className: "flex items-center gap-2 px-3 py-2 rounded-lg border border-border bg-surface-hover",
51248
+ children: [
51249
+ /* @__PURE__ */ jsxs("span", {
51250
+ className: "text-xs font-medium text-foreground-subtle shrink-0",
51251
+ children: [count, " selected"]
51252
+ }),
51253
+ /* @__PURE__ */ jsx("div", {
51254
+ className: "h-3.5 w-px bg-border shrink-0",
51255
+ "aria-hidden": "true"
51256
+ }),
51257
+ /* @__PURE__ */ jsxs("div", {
51258
+ className: "flex items-center gap-1.5 flex-wrap",
51259
+ children: [
51260
+ /* @__PURE__ */ jsx("button", {
51261
+ type: "button",
51262
+ onClick: handleDisable,
51263
+ className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
51264
+ children: "Disable"
51265
+ }),
51266
+ /* @__PURE__ */ jsx("button", {
51267
+ type: "button",
51268
+ onClick: handleEnable,
51269
+ className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
51270
+ children: "Enable"
51271
+ }),
51272
+ pendingDelete ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("button", {
51273
+ type: "button",
51274
+ onClick: handleDeleteConfirm,
51275
+ className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-red-500/50 bg-red-500/10 text-red-400 hover:bg-red-500/20"),
51276
+ children: [
51277
+ "Confirm delete ",
51278
+ count,
51279
+ "?"
51280
+ ]
51281
+ }), /* @__PURE__ */ jsx("button", {
51282
+ type: "button",
51283
+ onClick: handleDeleteCancel,
51284
+ className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
51285
+ children: "Cancel"
51286
+ })] }) : /* @__PURE__ */ jsx("button", {
51287
+ type: "button",
51288
+ onClick: handleDeleteFirst,
51289
+ className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-red-500/40 bg-red-500/10 text-red-400 hover:bg-red-500/20"),
51290
+ children: "Delete"
51291
+ }),
51292
+ /* @__PURE__ */ jsx("button", {
51293
+ type: "button",
51294
+ onClick: handleClear,
51295
+ className: cn("inline-flex items-center h-6 px-2.5 rounded-md border text-[10.5px] font-medium transition-colors", "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface"),
51296
+ children: "Clear"
51297
+ })
51298
+ ]
51299
+ })
51300
+ ]
51301
+ });
51302
+ }
51303
+ //#endregion
51304
+ //#region src/composites/device-list/batch-actions-bar.tsx
51305
+ /**
51306
+ * <DeviceBatchActionsBar> — responsive wrapper around <DeviceBatchToolbar>.
51307
+ *
51308
+ * Surfaces the available bulk actions once devices are selected:
51309
+ * - L2+ (`sm` and up): the toolbar renders inline as a row of buttons.
51310
+ * - L1 (`< sm`): a compact "Actions · N" trigger opens a popover that
51311
+ * hosts the SAME <DeviceBatchToolbar>, so the action logic (inline
51312
+ * delete-confirm, clear) is reused verbatim — never forked.
51313
+ *
51314
+ * Renders nothing while the selection is empty (delegated to the toolbar).
51315
+ */
51316
+ function DeviceBatchActionsBar(props) {
51317
+ const [open, setOpen] = useState(false);
51318
+ const rootRef = useRef(null);
51319
+ const count = props.selectedIds.size;
51320
+ useEffect(() => {
51321
+ if (!open) return;
51322
+ const onOutside = (e) => {
51323
+ const target = e.target instanceof Node ? e.target : null;
51324
+ if (target && rootRef.current && !rootRef.current.contains(target)) setOpen(false);
51325
+ };
51326
+ const onKey = (e) => {
51327
+ if (e.key === "Escape") setOpen(false);
51328
+ };
51329
+ document.addEventListener("mousedown", onOutside);
51330
+ document.addEventListener("touchstart", onOutside);
51331
+ document.addEventListener("keydown", onKey);
51332
+ return () => {
51333
+ document.removeEventListener("mousedown", onOutside);
51334
+ document.removeEventListener("touchstart", onOutside);
51335
+ document.removeEventListener("keydown", onKey);
51336
+ };
51337
+ }, [open]);
51338
+ if (count === 0) return null;
51339
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
51340
+ className: "hidden sm:block",
51341
+ children: /* @__PURE__ */ jsx(DeviceBatchToolbar, { ...props })
51342
+ }), /* @__PURE__ */ jsxs("div", {
51343
+ ref: rootRef,
51344
+ "data-testid": "batch-actions-compact",
51345
+ className: "relative sm:hidden",
51346
+ children: [/* @__PURE__ */ jsxs("button", {
51347
+ type: "button",
51348
+ onClick: () => setOpen((v) => !v),
51349
+ "aria-haspopup": "menu",
51350
+ "aria-expanded": open,
51351
+ className: "inline-flex items-center gap-1.5 h-7 px-2.5 rounded-full border border-primary/40 bg-primary/15 text-[10.5px] font-medium text-primary",
51352
+ children: [/* @__PURE__ */ jsxs("span", { children: ["Actions · ", String(count)] }), /* @__PURE__ */ jsx(ChevronDown, { className: `h-3 w-3 transition-transform ${open ? "rotate-180" : ""}` })]
51353
+ }), open && /* @__PURE__ */ jsx("div", {
51354
+ role: "menu",
51355
+ className: "absolute left-0 top-full mt-1 z-30 rounded-lg border border-border bg-surface shadow-md",
51356
+ children: /* @__PURE__ */ jsx(DeviceBatchToolbar, { ...props })
51357
+ })]
51358
+ })] });
50642
51359
  }
50643
51360
  //#endregion
50644
51361
  //#region src/hooks/use-debounced-string.ts
@@ -50659,6 +51376,59 @@ function useDebouncedString(value, delayMs) {
50659
51376
  return debounced;
50660
51377
  }
50661
51378
  //#endregion
51379
+ //#region src/hooks/use-persisted-page-size.ts
51380
+ /**
51381
+ * usePersistedPageSize — a "rows per page" choice remembered per viewer.
51382
+ *
51383
+ * The caller names the storage key, the allowed sizes and the default. Each
51384
+ * surface with its own default or option set keeps its own key, so one
51385
+ * surface's choice never lands on another that cannot show it (the device
51386
+ * list's global key stays `useDeviceListPageSize`'s, with its own options).
51387
+ *
51388
+ * `PAGE_SIZE_ALL` (0) is the "All" option: one page holding every row.
51389
+ *
51390
+ * Storage is a convenience, never a requirement: every read and write is
51391
+ * guarded, and a blocked / private-mode / missing storage simply means the
51392
+ * choice lasts until the page is left. A stored value that is not one of the
51393
+ * options (an older option set, a hand-edited value) is ignored.
51394
+ */
51395
+ /** The "All" option — one page holding every row. */
51396
+ var PAGE_SIZE_ALL = 0;
51397
+ function readStored(key, options, fallback) {
51398
+ if (key === null || typeof window === "undefined") return fallback;
51399
+ try {
51400
+ const raw = window.localStorage.getItem(key);
51401
+ if (raw === null) return fallback;
51402
+ const parsed = Number.parseInt(raw, 10);
51403
+ return Number.isFinite(parsed) && options.includes(parsed) ? parsed : fallback;
51404
+ } catch {
51405
+ return fallback;
51406
+ }
51407
+ }
51408
+ function writeStored(key, value) {
51409
+ if (key === null || typeof window === "undefined") return;
51410
+ try {
51411
+ window.localStorage.setItem(key, String(value));
51412
+ } catch {}
51413
+ }
51414
+ /** @returns `[size, setSize]`; `setSize` ignores a size that is not an option. */
51415
+ function usePersistedPageSize({ storageKey, options, defaultSize }) {
51416
+ const seed = options.includes(defaultSize) ? defaultSize : options[0] ?? defaultSize;
51417
+ const [size, setSize] = useState(() => readStored(storageKey, options, seed));
51418
+ return [size, useCallback((next) => {
51419
+ if (!options.includes(next)) return;
51420
+ setSize(next);
51421
+ writeStored(storageKey, next);
51422
+ }, [options, storageKey])];
51423
+ }
51424
+ /**
51425
+ * The number of rows one page holds for a chosen size: "All" is every row
51426
+ * (at least 1, so the pager arithmetic never divides by zero).
51427
+ */
51428
+ function effectivePageSize(size, totalItems) {
51429
+ return size === 0 ? Math.max(1, totalItems) : size;
51430
+ }
51431
+ //#endregion
50662
51432
  //#region src/hooks/use-device-list-page-size.ts
50663
51433
  /**
50664
51434
  * useDeviceListPageSize — global page-size preference for the device-list
@@ -50669,8 +51439,9 @@ function useDebouncedString(value, delayMs) {
50669
51439
  * value is validated against {@link DEVICE_LIST_PAGE_SIZE_OPTIONS}; any
50670
51440
  * garbage / out-of-range value falls back to the provided default.
50671
51441
  *
50672
- * SSR / jsdom-safe: every `window` access is guarded so the hook can run in
50673
- * a non-browser environment without throwing.
51442
+ * A thin binding of `usePersistedPageSize` to that key and option set; a
51443
+ * surface with a different default or options uses the generic hook with a
51444
+ * key of its own.
50674
51445
  */
50675
51446
  /** Selectable per-page sizes. The table default (50) is intentionally one of them. */
50676
51447
  var DEVICE_LIST_PAGE_SIZE_OPTIONS = [
@@ -50681,33 +51452,6 @@ var DEVICE_LIST_PAGE_SIZE_OPTIONS = [
50681
51452
  ];
50682
51453
  /** Single global key shared across every device-list surface. */
50683
51454
  var DEVICE_LIST_PAGE_SIZE_KEY = "camstack.device-list.pageSize";
50684
- function isAllowed(value) {
50685
- return DEVICE_LIST_PAGE_SIZE_OPTIONS.includes(value);
50686
- }
50687
- /**
50688
- * Normalize a caller-supplied `defaultSize` to an allowed option, falling back
50689
- * to the canonical default so a stray value can never seed a phantom size.
50690
- */
50691
- function safeDefault(defaultSize) {
50692
- return isAllowed(defaultSize) ? defaultSize : 50;
50693
- }
50694
- function readStored(defaultSize) {
50695
- if (typeof window === "undefined") return defaultSize;
50696
- try {
50697
- const raw = window.localStorage.getItem(DEVICE_LIST_PAGE_SIZE_KEY);
50698
- if (raw === null) return defaultSize;
50699
- const parsed = Number.parseInt(raw, 10);
50700
- return Number.isFinite(parsed) && isAllowed(parsed) ? parsed : defaultSize;
50701
- } catch {
50702
- return defaultSize;
50703
- }
50704
- }
50705
- function writeStored(value) {
50706
- if (typeof window === "undefined") return;
50707
- try {
50708
- window.localStorage.setItem(DEVICE_LIST_PAGE_SIZE_KEY, String(value));
50709
- } catch {}
50710
- }
50711
51455
  /**
50712
51456
  * @param defaultSize fallback when nothing valid is stored. If it is not itself
50713
51457
  * a member of {@link DEVICE_LIST_PAGE_SIZE_OPTIONS}, it is replaced by
@@ -50715,13 +51459,11 @@ function writeStored(value) {
50715
51459
  * @returns `[size, setSize]` — `setSize` ignores values outside the allowed set.
50716
51460
  */
50717
51461
  function useDeviceListPageSize(defaultSize) {
50718
- const seed = safeDefault(defaultSize);
50719
- const [size, setSize] = useState(() => readStored(seed));
50720
- return [size, useCallback((next) => {
50721
- if (!isAllowed(next)) return;
50722
- setSize(next);
50723
- writeStored(next);
50724
- }, [])];
51462
+ return usePersistedPageSize({
51463
+ storageKey: DEVICE_LIST_PAGE_SIZE_KEY,
51464
+ options: DEVICE_LIST_PAGE_SIZE_OPTIONS,
51465
+ defaultSize: DEVICE_LIST_PAGE_SIZE_OPTIONS.includes(defaultSize) ? defaultSize : 50
51466
+ });
50725
51467
  }
50726
51468
  //#endregion
50727
51469
  //#region src/composites/device-item/helpers.ts
@@ -53019,6 +53761,29 @@ function groupTopLevelAndAccessories(devices, options) {
53019
53761
  };
53020
53762
  }
53021
53763
  //#endregion
53764
+ //#region src/composites/device-list/page-size-select.tsx
53765
+ function pageSizeLabel(size) {
53766
+ return size === 0 ? "All" : String(size);
53767
+ }
53768
+ function PageSizeSelect({ value, options, onChange, ariaLabel }) {
53769
+ return /* @__PURE__ */ jsxs("div", {
53770
+ className: "inline-flex items-center gap-1.5",
53771
+ children: [/* @__PURE__ */ jsx("span", {
53772
+ "aria-hidden": "true",
53773
+ children: "Per page"
53774
+ }), /* @__PURE__ */ jsx("select", {
53775
+ "aria-label": ariaLabel,
53776
+ value,
53777
+ onChange: (e) => onChange(Number.parseInt(e.target.value, 10)),
53778
+ className: "h-7 px-1.5 rounded-md border border-border bg-surface text-[11px] text-foreground focus:outline-none focus:ring-1 focus:ring-primary/40",
53779
+ children: options.map((opt) => /* @__PURE__ */ jsx("option", {
53780
+ value: opt,
53781
+ children: pageSizeLabel(opt)
53782
+ }, opt))
53783
+ })]
53784
+ });
53785
+ }
53786
+ //#endregion
53022
53787
  //#region src/composites/device-list/multi-select.tsx
53023
53788
  /**
53024
53789
  * Compact multi-select dropdown — a single chip-sized trigger ("Label · N")
@@ -53933,23 +54698,11 @@ function DeviceModeList(props) {
53933
54698
  totalItems,
53934
54699
  " ",
53935
54700
  totalItems === 1 ? "device" : "devices"
53936
- ] }), /* @__PURE__ */ jsxs("div", {
53937
- className: "inline-flex items-center gap-1.5",
53938
- children: [/* @__PURE__ */ jsx("span", {
53939
- "aria-hidden": "true",
53940
- children: "Per page"
53941
- }), /* @__PURE__ */ jsx("select", {
53942
- "aria-label": "Devices per page",
53943
- value: tablePageSize,
53944
- onChange: (e) => {
53945
- setTablePageSize(Number.parseInt(e.target.value, 10));
53946
- },
53947
- className: "h-7 px-1.5 rounded-md border border-border bg-surface text-[11px] text-foreground focus:outline-none focus:ring-1 focus:ring-primary/40",
53948
- children: DEVICE_LIST_PAGE_SIZE_OPTIONS.map((opt) => /* @__PURE__ */ jsx("option", {
53949
- value: opt,
53950
- children: opt
53951
- }, opt))
53952
- })]
54701
+ ] }), /* @__PURE__ */ jsx(PageSizeSelect, {
54702
+ ariaLabel: "Devices per page",
54703
+ value: tablePageSize,
54704
+ options: DEVICE_LIST_PAGE_SIZE_OPTIONS,
54705
+ onChange: setTablePageSize
53953
54706
  })]
53954
54707
  }), /* @__PURE__ */ jsx(Pagination, {
53955
54708
  currentPage: safePage,
@@ -53993,6 +54746,7 @@ function useDeviceListRealtimeSync() {
53993
54746
  }
53994
54747
  //#endregion
53995
54748
  //#region src/composites/device-list/generic-mode.tsx
54749
+ var FIXED_PAGE_SIZE = 100;
53996
54750
  /**
53997
54751
  * Generic render-prop list: search + pagination chrome shared with the device
53998
54752
  * list, over an arbitrary item type. Local (non-URL) state — intended for
@@ -54000,7 +54754,12 @@ function useDeviceListRealtimeSync() {
54000
54754
  * `renderItem`.
54001
54755
  */
54002
54756
  function GenericModeList(props) {
54003
- const { items, getKey, getSearchText, renderItem, getFilterType, mapFilterOption, filterOptions, filterLabel = "Type", initialTypeFilters = [], columns, renderExpanded, isExpanded, rowClassName, onRowClick, isLoading = false, searchPlaceholder = "Search", emptyLabel = "No items found.", pageSize = 100, toolbar } = props;
54757
+ const { items, getKey, getSearchText, renderItem, getFilterType, mapFilterOption, filterOptions, filterLabel = "Type", initialTypeFilters = [], columns, renderExpanded, isExpanded, rowClassName, onRowClick, isLoading = false, searchPlaceholder = "Search", emptyLabel = "No items found.", pageSize: fixedPageSize = FIXED_PAGE_SIZE, pageSizeSelector, toolbar } = props;
54758
+ const [chosenPageSize, setChosenPageSize] = usePersistedPageSize(pageSizeSelector ?? {
54759
+ storageKey: null,
54760
+ options: [fixedPageSize],
54761
+ defaultSize: fixedPageSize
54762
+ });
54004
54763
  const [searchInput, setSearchInput] = useState("");
54005
54764
  const debouncedSearch = useDebouncedString(searchInput, 300);
54006
54765
  const [typeFilters, setTypeFilters] = useState(() => [...initialTypeFilters]);
@@ -54049,6 +54808,7 @@ function GenericModeList(props) {
54049
54808
  getFilterType
54050
54809
  ]);
54051
54810
  const totalItems = filtered.length;
54811
+ const pageSize = pageSizeSelector ? effectivePageSize(chosenPageSize, totalItems) : fixedPageSize;
54052
54812
  const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
54053
54813
  const safePage = Math.min(page, totalPages);
54054
54814
  const startIdx = (safePage - 1) * pageSize;
@@ -54132,7 +54892,23 @@ function GenericModeList(props) {
54132
54892
  }),
54133
54893
  /* @__PURE__ */ jsx("div", {
54134
54894
  className: "shrink-0",
54135
- children: /* @__PURE__ */ jsx(Pagination, {
54895
+ children: pageSizeSelector && totalItems > 0 ? /* @__PURE__ */ jsxs("div", {
54896
+ className: "flex items-center justify-between gap-4 flex-wrap pt-2 text-[11px] text-foreground-subtle",
54897
+ children: [/* @__PURE__ */ jsxs("div", {
54898
+ className: "inline-flex items-center gap-2",
54899
+ children: [totalItems <= pageSize && /* @__PURE__ */ jsxs("span", { children: [totalItems, " total"] }), /* @__PURE__ */ jsx(PageSizeSelect, {
54900
+ ariaLabel: "Rows per page",
54901
+ value: chosenPageSize,
54902
+ options: pageSizeSelector.options,
54903
+ onChange: setChosenPageSize
54904
+ })]
54905
+ }), /* @__PURE__ */ jsx(Pagination, {
54906
+ currentPage: safePage,
54907
+ totalItems,
54908
+ pageSize,
54909
+ onPageChange: setPage
54910
+ })]
54911
+ }) : /* @__PURE__ */ jsx(Pagination, {
54136
54912
  currentPage: safePage,
54137
54913
  totalItems,
54138
54914
  pageSize,
@@ -54261,6 +55037,24 @@ var DeviceItemRowSchema = z.object({
54261
55037
  });
54262
55038
  var SupportedKindsSchema = z.array(z.string());
54263
55039
  var STATUS_POLL_INTERVAL_MS = 1e4;
55040
+ /** Where the viewer's "rows per page" choice for this table is remembered. */
55041
+ var DEVICE_EXPORT_PAGE_SIZE_KEY = "camstack.device-export.pageSize";
55042
+ /**
55043
+ * The exposed-devices table's page sizes. Its own key and options, not the
55044
+ * Devices page's (25/50/100/200, default 50): the operator asked for a short
55045
+ * default here (25) and an "All" for a quick scan of every exportable device.
55046
+ */
55047
+ var DEVICE_EXPORT_PAGE_SIZE = {
55048
+ options: [
55049
+ 10,
55050
+ 25,
55051
+ 50,
55052
+ 100,
55053
+ 0
55054
+ ],
55055
+ defaultSize: 25,
55056
+ storageKey: DEVICE_EXPORT_PAGE_SIZE_KEY
55057
+ };
54264
55058
  /**
54265
55059
  * The fleet-list poll. Deliberately the same 30 s as the window
54266
55060
  * `applyFleetQueryDefaults` registers for `deviceManager.listAll` (D274) —
@@ -54345,6 +55139,35 @@ function SetupSection({ setup }) {
54345
55139
  })]
54346
55140
  });
54347
55141
  }
55142
+ /** Parse `getStatus` against the canonical cap schema (loose wire output). */
55143
+ function parseStatus(data) {
55144
+ const parsed = DeviceExportStatusSchema.safeParse(data);
55145
+ return parsed.success ? parsed.data : null;
55146
+ }
55147
+ /**
55148
+ * The exporter's error and Setup block on their own — for a page that shows
55149
+ * them above its settings while the devices table sits at the bottom. Renders
55150
+ * nothing when the exporter reports neither. Shares `getStatus`'s query key
55151
+ * with the panel, so mounting both costs one request.
55152
+ */
55153
+ function DeviceExportSetup({ addonId }) {
55154
+ const statusQuery = useDeviceExportGetStatus({
55155
+ nodeId: "hub",
55156
+ addonId
55157
+ }, {
55158
+ refetchInterval: STATUS_POLL_INTERVAL_MS,
55159
+ retry: false
55160
+ });
55161
+ const status = useMemo(() => parseStatus(statusQuery.data), [statusQuery.data]);
55162
+ if (!status?.error && !status?.setup) return null;
55163
+ return /* @__PURE__ */ jsxs("div", {
55164
+ className: "rounded-lg border border-border bg-surface overflow-hidden",
55165
+ children: [status.error && /* @__PURE__ */ jsx("div", {
55166
+ className: "px-2 sm:px-4 py-3 border-b border-border",
55167
+ children: /* @__PURE__ */ jsx(ErrorBox, { message: status.error })
55168
+ }), status.setup && /* @__PURE__ */ jsx(SetupSection, { setup: status.setup })]
55169
+ });
55170
+ }
54348
55171
  /**
54349
55172
  * Per-row Expose/Exposed toggle. Reflects whether the row's device is exposed
54350
55173
  * to THIS exporter and flips it through the SAME runner the bulk buttons use —
@@ -54423,7 +55246,7 @@ function BulkSummary({ outcome, onDismiss }) {
54423
55246
  * Generic device-export panel. Addon-agnostic — only the `device-export`
54424
55247
  * cap drives it.
54425
55248
  */
54426
- function DeviceExportPanel({ addonId, onOpenDevice }) {
55249
+ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
54427
55250
  const queryClient = useQueryClient();
54428
55251
  const statusQuery = useDeviceExportGetStatus({
54429
55252
  nodeId: "hub",
@@ -54449,10 +55272,7 @@ function DeviceExportPanel({ addonId, onOpenDevice }) {
54449
55272
  }, [queryClient]);
54450
55273
  const exposeMutation = useDeviceExportExposeDevice({ onSuccess: invalidate });
54451
55274
  const unexposeMutation = useDeviceExportUnexposeDevice({ onSuccess: invalidate });
54452
- const status = useMemo(() => {
54453
- const parsed = DeviceExportStatusSchema.safeParse(statusQuery.data);
54454
- return parsed.success ? parsed.data : null;
54455
- }, [statusQuery.data]);
55275
+ const status = useMemo(() => parseStatus(statusQuery.data), [statusQuery.data]);
54456
55276
  const exposed = useMemo(() => {
54457
55277
  const parsed = ExposedDeviceArraySchema.safeParse(exposedQuery.data);
54458
55278
  return parsed.success ? parsed.data : [];
@@ -54675,11 +55495,11 @@ function DeviceExportPanel({ addonId, onOpenDevice }) {
54675
55495
  ]
54676
55496
  }), loading && /* @__PURE__ */ jsx(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin text-foreground-subtle" })]
54677
55497
  }),
54678
- status?.error && /* @__PURE__ */ jsx("div", {
55498
+ showSetup && status?.error && /* @__PURE__ */ jsx("div", {
54679
55499
  className: "px-2 sm:px-4 py-3 border-b border-border",
54680
55500
  children: /* @__PURE__ */ jsx(ErrorBox, { message: status.error })
54681
55501
  }),
54682
- status?.setup && /* @__PURE__ */ jsx(SetupSection, { setup: status.setup }),
55502
+ showSetup && status?.setup && /* @__PURE__ */ jsx(SetupSection, { setup: status.setup }),
54683
55503
  outcome !== null && /* @__PURE__ */ jsx(BulkSummary, {
54684
55504
  outcome,
54685
55505
  onDismiss: dismissOutcome
@@ -54699,6 +55519,7 @@ function DeviceExportPanel({ addonId, onOpenDevice }) {
54699
55519
  columns,
54700
55520
  onRowClick: onOpenDevice ? onRowClick : void 0,
54701
55521
  toolbar: selectionToolbar,
55522
+ pageSizeSelector: DEVICE_EXPORT_PAGE_SIZE,
54702
55523
  searchPlaceholder: "Search by name or stableId",
54703
55524
  emptyLabel: "No device matches this search."
54704
55525
  }) : /* @__PURE__ */ jsx(EmptyState, {
@@ -54724,145 +55545,6 @@ function DeviceGrid({ children, minCardWidth = 220, gap = 3, className }) {
54724
55545
  });
54725
55546
  }
54726
55547
  //#endregion
54727
- //#region src/composites/icon-picker.tsx
54728
- /**
54729
- * icon-picker — the ONE control for choosing an icon, wherever admin-ui asks.
54730
- *
54731
- * There were two: a device one over `DISPLAY_ICON_REGISTRY` and a room one
54732
- * over `LOCATION_ICON_IDS`, identical apart from the vocabulary they walked.
54733
- * That was tolerable while the device set was 56 glyphs in a flat grid. It is
54734
- * not at 157: a picker that only lets the operator HUNT is a picker they stop
54735
- * using, and the two copies were already drifting — only one of them had an
54736
- * i18n'd label.
54737
- *
54738
- * So the vocabulary, the grouping and the search all come from
54739
- * `@camstack/types`' `display-icon-vocabulary`, which is the same module the
54740
- * viewer's sheet searches. What is left here is the web half: a popover, a
54741
- * grid, and a text field.
54742
- *
54743
- * ## What it does NOT own
54744
- *
54745
- * - **The glyphs.** `resolveGlyph` is a prop, because this package's table is
54746
- * `lucide-react` and the viewer's is `lucide-react-native`. A key this build
54747
- * cannot draw is SKIPPED rather than rendered as a hole — an empty tile
54748
- * reads as a rendering bug, not as a missing choice.
54749
- * - **The words.** Every string is a prop. This package has no i18n (one file
54750
- * in it imports react-i18next, and that is the exception), and a control
54751
- * that reached for a translation would force it on every consumer.
54752
- * - **The write.** `onPick` is called and that is all; whether it is a form
54753
- * value or an immediate mutation is the caller's business, as it already was
54754
- * in both pickers this replaces.
54755
- *
54756
- * ## The clear tile is never filtered away
54757
- *
54758
- * The "inherit" / "no icon" choice sits above the sections and ignores the
54759
- * search. A query that hid it would strand the operator on a glyph they picked
54760
- * by accident with no way back to the default — the one action a picker must
54761
- * always offer.
54762
- */
54763
- function IconPicker({ value, onPick, resolveGlyph, fallbackGlyph, labels, only, extraWords, clearGlyph, labelFor, disabled = false }) {
54764
- const [open, setOpen] = useState(false);
54765
- const [query, setQuery] = useState("");
54766
- const allowed = useMemo(() => only === void 0 ? null : new Set(only), [only]);
54767
- const sections = useMemo(() => {
54768
- const hits = searchDisplayIcons(query, extraWords);
54769
- const byGroup = /* @__PURE__ */ new Map();
54770
- for (const hit of hits) {
54771
- const { key, group } = hit.entry;
54772
- if (allowed !== null && !allowed.has(key)) continue;
54773
- if (resolveGlyph(key) === void 0) continue;
54774
- const bucket = byGroup.get(group);
54775
- if (bucket === void 0) byGroup.set(group, [key]);
54776
- else bucket.push(key);
54777
- }
54778
- return DISPLAY_ICON_GROUPS.flatMap((group) => {
54779
- const keys = byGroup.get(group);
54780
- return keys === void 0 || keys.length === 0 ? [] : [{
54781
- group,
54782
- keys
54783
- }];
54784
- });
54785
- }, [
54786
- query,
54787
- extraWords,
54788
- allowed,
54789
- resolveGlyph
54790
- ]);
54791
- const Current = (value === null ? void 0 : resolveGlyph(value)) ?? fallbackGlyph;
54792
- const choose = async (next) => {
54793
- setOpen(false);
54794
- setQuery("");
54795
- await onPick(next);
54796
- };
54797
- const Clear = clearGlyph ?? fallbackGlyph;
54798
- return /* @__PURE__ */ jsxs("div", {
54799
- className: "relative flex shrink-0 items-center gap-2",
54800
- children: [/* @__PURE__ */ jsx("button", {
54801
- type: "button",
54802
- disabled,
54803
- onClick: () => setOpen((o) => !o),
54804
- "aria-expanded": open,
54805
- "aria-label": labels.trigger,
54806
- title: labels.trigger,
54807
- className: "rounded border border-border p-1 text-foreground-muted hover:bg-surface-hover disabled:opacity-50",
54808
- children: /* @__PURE__ */ jsx(Current, { className: "h-4 w-4" })
54809
- }), open && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("button", {
54810
- type: "button",
54811
- "aria-label": labels.close,
54812
- onClick: () => setOpen(false),
54813
- className: "fixed inset-0 z-10 cursor-default"
54814
- }), /* @__PURE__ */ jsxs("div", {
54815
- role: "group",
54816
- "aria-label": labels.panel,
54817
- className: "absolute left-0 top-full z-20 mt-1 max-h-80 w-72 overflow-y-auto rounded-md border border-border bg-surface p-2 shadow-lg",
54818
- children: [
54819
- /* @__PURE__ */ jsx("input", {
54820
- type: "search",
54821
- value: query,
54822
- autoFocus: true,
54823
- onChange: (e) => setQuery(e.target.value),
54824
- placeholder: labels.searchPlaceholder,
54825
- "aria-label": labels.searchPlaceholder,
54826
- className: "mb-2 w-full rounded border border-border bg-surface-raised px-2 py-1 text-xs text-foreground placeholder:text-foreground-subtle"
54827
- }),
54828
- /* @__PURE__ */ jsxs("button", {
54829
- type: "button",
54830
- onClick: () => void choose(null),
54831
- "aria-pressed": value === null,
54832
- className: "mb-2 flex w-full items-center gap-1.5 rounded px-1.5 py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover",
54833
- children: [/* @__PURE__ */ jsx(Clear, { className: "h-3.5 w-3.5" }), labels.clear]
54834
- }),
54835
- sections.length === 0 ? /* @__PURE__ */ jsx("p", {
54836
- className: "px-1.5 py-2 text-[11px] text-foreground-subtle",
54837
- children: labels.noMatches
54838
- }) : sections.map(({ group, keys }) => /* @__PURE__ */ jsxs("section", {
54839
- className: "mb-2 last:mb-0",
54840
- children: [/* @__PURE__ */ jsx("h4", {
54841
- className: "px-1.5 pb-1 text-[10px] uppercase tracking-wide text-foreground-subtle",
54842
- children: labels.groups[group]
54843
- }), /* @__PURE__ */ jsx("div", {
54844
- className: "grid grid-cols-7 gap-1",
54845
- children: keys.map((key) => {
54846
- const Glyph = resolveGlyph(key);
54847
- if (Glyph === void 0) return null;
54848
- const selected = value === key;
54849
- return /* @__PURE__ */ jsx("button", {
54850
- type: "button",
54851
- onClick: () => void choose(key),
54852
- "aria-pressed": selected,
54853
- "aria-label": labelFor?.(key) ?? key,
54854
- title: labelFor?.(key) ?? key,
54855
- className: selected ? "flex items-center justify-center rounded border border-primary bg-primary/10 p-1.5 text-foreground" : "flex items-center justify-center rounded border border-transparent p-1.5 text-foreground-muted hover:bg-surface-hover",
54856
- children: /* @__PURE__ */ jsx(Glyph, { className: "h-4 w-4" })
54857
- }, key);
54858
- })
54859
- })]
54860
- }, group))
54861
- ]
54862
- })] })]
54863
- });
54864
- }
54865
- //#endregion
54866
55548
  //#region src/composites/device-selector-picker.tsx
54867
55549
  /**
54868
55550
  * <DeviceSelectorPicker> — the operator-facing editor for a v3
@@ -60996,4 +61678,4 @@ var SceneMonitorEditor = lazy(() => import("./SceneMonitorEditor-vfWTm6qv.js").t
60996
61678
  /** Lazy-wrapped `PrivacyMaskSettings` — code-split off the main bundle. */
60997
61679
  var PrivacyMaskSettings = lazy(() => import("./PrivacyMaskSettings-DMkchqO1.js").then((m) => ({ default: m.PrivacyMaskSettings })));
60998
61680
  //#endregion
60999
- 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, BackToStepsButton, 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, GateBadge, GateControl, 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, LayeredSettingsForm, 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, 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, RootModelSourceControl, RootSuppliedModelNote, 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, STEP_DOCK_WIDTH_PX, SceneMonitorEditor, ScopePicker, ScriptHeroCard, ScriptInlineControl, ScrollArea, SectionCard, SegmentedChoice, Select, SelectedChip, SemanticBadge, SensorHeroCard, SensorInlineControl, SensorValueAtom, Separator, SettingRow, Sidebar, SidebarItem, Skeleton, SlideToggle, SnapshotButton, SortHeaderButton, StatCard, StateValuesStream, StatusBadge, StepDetailDock, StepInspectorPanel, StepModelRow, StepTimings, 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, acceleratorLabel, acceleratorShortLabel, agentColumnKey, allDeviceTypeFilterOptions, applyFleetQueryDefaults, applyPickerFilters, ariaSortForColumn, badgeFor, bindsToModel, 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, defaultOfField, defaultTheme, deriveDeviceKind, deriveWebrtcDebugStats, describeDeviceSelector, deviceIntegrationResolver, deviceListStateOf, deviceMatchesFilter, deviceOptionLabel, deviceRoleMeta, deviceRoleMetaOf, deviceTypeLabel, deviceTypeMeta, deviceTypeMetaOf, devicesToOptions, dockBox, draftFromQuery, emptyDevicesWidens, emptySessionRun, ensureMfHostInit, eventKindLabel, filterDataTableRows, filterDeviceOptions, filterOptions as filterOptionSelectOptions, filterPickerDevices, finalizeSessionRun, findTimezone, flattenPickerGrouping, flattenPickerRows, fleetViewOf, formatControlDateTime, formatLastSeen, formatNumeric, formatSettingValue, freezeVerdict, fuzzyMatch, getClassColor, getPhaseVisual, groupAgentColumns, groupChildrenByLayout, groupPickerDevices, groupPickerDevicesBy, hardwareLabel, hubOrigin, hubPath, hubUrl, hubWsUrl, humanDuration, humidifierTint, Mic as i, initialScrubState, insertAtSelection, isAbsentProvider, isDataTableFilterActive, isFieldVisible, isGroupExpanded, isValueField, Camera as l, lawnMowerActivityMeta, lightColors, loadRemoteBundle, makeScrubBridge, metadataEntries, metadataString, mirror, mountAddonPage, mountPath, Square as n, nextGridPosition, 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, searchSteps, selectPickerGroup, selectedDeviceOptions, serializeRecordedCommand, settingsModelIdFor, shouldCommit, shouldEmit, shouldShowAddNewFooter, shouldShowUseQueryRow, shouldUseSingleNode, showsNoCamerasHint, sortRows, sortRowsByColumn, statusIcons, statusLine, stepFieldDomId, stepHasModelForFormat, stepLocalFields, 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, useAddonsListInstallations, 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, useDecoderGetInfo, useDecoderReprobeHwaccel, 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, usePipelineExecutorDownloadModel, usePipelineExecutorGetAddonModels, usePipelineExecutorGetAudioCapabilities, usePipelineExecutorGetAvailableEngines, usePipelineExecutorGetCapabilities, usePipelineExecutorGetDefaultSteps, usePipelineExecutorGetDetectionConfigSchema, usePipelineExecutorGetEffectiveTuning, usePipelineExecutorGetEngineProvisioning, usePipelineExecutorGetGlobalPipelineConfig, usePipelineExecutorGetGlobalSteps, usePipelineExecutorGetInferenceDeviceHealth, usePipelineExecutorGetOrchestratorConfigSchema, usePipelineExecutorGetReferenceAudio, usePipelineExecutorGetReferenceAudioFiles, usePipelineExecutorGetReferenceImage, usePipelineExecutorGetSchema, usePipelineExecutorGetSelectedEngine, usePipelineExecutorGetVideoPipelineSteps, usePipelineExecutorInferCached, usePipelineExecutorKillEngine, usePipelineExecutorListLoadedEngines, usePipelineExecutorListReferenceImages, usePipelineExecutorRearmInferenceDevice, usePipelineExecutorRunAudioTest, usePipelineExecutorRunPipeline, usePipelineExecutorRunPipelineBatch, usePipelineExecutorSetVideoPipelineSteps, usePipelineExecutorSpinEngine, usePipelineExecutorUncacheFrame, usePipelineExecutorValidatePipeline, usePipelineOrchestratorApplyAutoTune, usePipelineOrchestratorApplyDeviceSettingsPatch, usePipelineOrchestratorAssignAudio, usePipelineOrchestratorAssignPipeline, 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, usePipelineOrchestratorPauseForStorageMigration, usePipelineOrchestratorPlanAutoTune, usePipelineOrchestratorRebalance, usePipelineOrchestratorReconcileFromDisk, usePipelineOrchestratorRemoveAgentSettings, usePipelineOrchestratorRequestOccupancyLook, usePipelineOrchestratorResetNodePipelineDefaults, usePipelineOrchestratorResolvePipeline, usePipelineOrchestratorResumeForStorageMigration, usePipelineOrchestratorSetAgentCapabilities, usePipelineOrchestratorSetAgentDecodeLimits, usePipelineOrchestratorSetAgentInferenceDevices, usePipelineOrchestratorSetAgentMaxCameras, usePipelineOrchestratorSetAgentReachableHost, usePipelineOrchestratorSetCameraPipelineForAgent, usePipelineOrchestratorSetCameraSensors, usePipelineOrchestratorSetCameraStepOverride, usePipelineOrchestratorSetCameraStepSettings, usePipelineOrchestratorSetCameraStepToggle, usePipelineOrchestratorSetCameraSwitch, usePipelineOrchestratorSetCapabilityBinding, usePipelineOrchestratorSetLoadBalancingShares, usePipelineOrchestratorSetPipelineDevicePin, usePipelineOrchestratorSetShareSource, usePipelineOrchestratorSuggestShares, usePipelineOrchestratorUnassignAudio, usePipelineOrchestratorUnassignPipeline, 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, useStreamBrokerRegenerateRtspToken, useStreamBrokerReleaseEgressTranscode, useStreamBrokerReleaseStreamWithCodec, useStreamBrokerRenderPreBufferClip, useStreamBrokerReportStreamSignal, useStreamBrokerRestartProfile, useStreamBrokerRetractCameraStream, useStreamBrokerSetDeviceAudioMute, useStreamBrokerSetPreBufferDuration, useStreamBrokerSetRtspEnabled, useStreamBrokerSubscribeAudioChunks, useStreamBrokerUnassignProfile, useStreamBrokerUnsubscribeAudioChunks, 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, withUseRootModel, writeStoredGroupBy };
61681
+ 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, BackToStepsButton, 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_EXPORT_PAGE_SIZE_KEY, 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, DeviceExportSetup, 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, GateBadge, GateControl, 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, LayeredSettingsForm, 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, PAGE_SIZE_ALL, 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, PageSizeSelect, PhaseIcon, PipelineBuilder, PipelineRuntimeSelector, PipelineStep, 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, RootModelSourceControl, RootSuppliedModelNote, RunReport, SCENE_CAPTURE_VARIANTS, SECRET_REVEAL_MS, 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, STEP_DOCK_WIDTH_PX, SceneMonitorEditor, ScopePicker, ScriptHeroCard, ScriptInlineControl, ScrollArea, SecretFieldControl, SecretRevealProvider, SectionCard, SegmentedChoice, Select, SelectedChip, SemanticBadge, SendFrameToTrainingButton, SensorHeroCard, SensorInlineControl, SensorValueAtom, Separator, SettingRow, Sidebar, SidebarItem, Skeleton, SlideToggle, SnapshotButton, SortHeaderButton, StatCard, StateValuesStream, StatusBadge, StepDetailDock, StepInspectorPanel, StepModelRow, StepTimings, 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, TrainingCollectionPicker, UnitPicker, VacuumHeroCard, VacuumInlineControl, ValueReadout, ValveHeroCard, ValveInlineControl, VersionBadge, VodPlaybackProvider, WaterHeaterHeroCard, WaterHeaterInlineControl, WeatherHeroCard, WeatherInlineControl, WidgetMetricCard, WidgetPanel, WidgetRegistryProvider, WidgetSlot, ZoneEditingProvider, MicOff as a, acceleratorLabel, acceleratorShortLabel, agentColumnKey, allDeviceTypeFilterOptions, applyFleetQueryDefaults, applyPickerFilters, ariaSortForColumn, badgeFor, bindsToModel, 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, defaultOfField, defaultTheme, deriveDeviceKind, deriveWebrtcDebugStats, describeDeviceSelector, deviceIntegrationResolver, deviceListStateOf, deviceMatchesFilter, deviceOptionLabel, deviceRoleMeta, deviceRoleMetaOf, deviceTypeLabel, deviceTypeMeta, deviceTypeMetaOf, devicesToOptions, dockBox, draftFromQuery, effectivePageSize, emptyDevicesWidens, emptySessionRun, ensureMfHostInit, eventKindLabel, filterDataTableRows, filterDeviceOptions, filterOptions as filterOptionSelectOptions, filterPickerDevices, finalizeSessionRun, findTimezone, flattenPickerGrouping, flattenPickerRows, fleetViewOf, formatControlDateTime, formatLastSeen, formatNumeric, formatSettingValue, freezeVerdict, fuzzyMatch, getClassColor, getPhaseVisual, groupAgentColumns, groupChildrenByLayout, groupPickerDevices, groupPickerDevicesBy, hardwareLabel, hubOrigin, hubPath, hubUrl, hubWsUrl, humanDuration, humidifierTint, Mic as i, initialScrubState, insertAtSelection, isAbsentProvider, isDataTableFilterActive, isFieldVisible, isGroupExpanded, isKeychainFieldValue, isValueField, Camera as l, lawnMowerActivityMeta, lightColors, loadRemoteBundle, makeScrubBridge, metadataEntries, metadataString, mirror, mountAddonPage, mountPath, Square as n, nextGridPosition, nextReconnectAction, nextSort, nextSortDirection, nextTableSort, normalizeForSearch, ImageOff as o, offListSelections, openTopLayer, optionSearchText as optionSelectSearchText, orphanDeviceLabel, overrideEntityIdFromLink, pageRows, pageSizeLabel, 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, searchSteps, selectPickerGroup, selectedDeviceOptions, serializeRecordedCommand, settingsModelIdFor, shouldCommit, shouldEmit, shouldShowAddNewFooter, shouldShowUseQueryRow, shouldUseSingleNode, showsNoCamerasHint, sortRows, sortRowsByColumn, statusIcons, statusLine, stepFieldDomId, stepHasModelForFormat, stepLocalFields, stepModelOptions, stripParentNamePrefix, summarizeHubCandidates, Trash2 as t, tabForSelector, tankAlert, themeToCss, toStationaryOverlayBox, toggleDataTableFacet, togglePickerDevice, togglePickerKey, trainingRefusalText, 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, useAddonsListInstallations, 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, useDecoderGetInfo, useDecoderReprobeHwaccel, 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, useKeychainAdminDeleteOrphans, useKeychainAdminDownloadRecoveryKey, useKeychainAdminListAudit, useKeychainAdminListSecrets, useKeychainAdminPurgeRetiredKeys, useKeychainAdminResetKeychain, useKeychainAdminReveal, useKeychainAdminRotateMasterKey, useKeychainAdminStatus, 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, usePersistedPageSize, usePetFeederCallPet, usePetFeederCancelFeed, usePetFeederFeed, usePetFeederGetStatus, usePetFeederMarkFoodReplenished, usePetFeederPlaySound, usePetFeederResetDesiccant, usePetFeederSetChildLock, usePetFeederSetFeedSound, usePetFeederSetIndicatorLight, usePetFeederSetVolume, usePickerSiteZones, usePipelineAnalyticsAddRetrainFrameFromRecording, usePipelineAnalyticsAddRetrainFramesToCollection, usePipelineAnalyticsApplyDeviceSettingsPatch, usePipelineAnalyticsCancelRelocateMedia, usePipelineAnalyticsCancelStorageMigrationMove, usePipelineAnalyticsClearTrackRelation, usePipelineAnalyticsClearTracks, usePipelineAnalyticsCompleteRetrainTrack, usePipelineAnalyticsCountRelocatableMedia, usePipelineAnalyticsCountUnstampedEventMedia, usePipelineAnalyticsCreateRetrainCollection, usePipelineAnalyticsDeleteDeviceEvents, usePipelineAnalyticsDeleteRetrainCollection, 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, usePipelineAnalyticsListRetrainAssistProfiles, usePipelineAnalyticsListRetrainCollectionFrames, usePipelineAnalyticsListRetrainCollections, usePipelineAnalyticsListRetrainFrames, usePipelineAnalyticsListRetrainStaging, usePipelineAnalyticsListTrackMedia, usePipelineAnalyticsListTrackRelations, usePipelineAnalyticsListTracks, usePipelineAnalyticsOwnersWithMedia, usePipelineAnalyticsPauseForStorageMigration, usePipelineAnalyticsProposeRetrainAnnotations, usePipelineAnalyticsProposeRetrainAnnotationsWithLlm, usePipelineAnalyticsPruneEvents, usePipelineAnalyticsPruneEventsBefore, usePipelineAnalyticsPruneTracksBefore, usePipelineAnalyticsReadLongTermSeries, usePipelineAnalyticsRebuildObjectEmbeddings, usePipelineAnalyticsReclaimDebugMedia, usePipelineAnalyticsReconcileFromDisk, usePipelineAnalyticsRefreshStorageLocationsForMigration, usePipelineAnalyticsRelocateMedia, usePipelineAnalyticsRemoveRetrainFrameFromCollection, usePipelineAnalyticsRenameRetrainCollection, usePipelineAnalyticsRestageRetrainTrack, usePipelineAnalyticsResumeForStorageMigration, usePipelineAnalyticsSaveRetrainAnnotations, usePipelineAnalyticsSearchObjectEvents, usePipelineAnalyticsSelectRetrainFrames, usePipelineAnalyticsSetRetrainFrameComplete, usePipelineAnalyticsSetTrackFlags, usePipelineAnalyticsSetTrackPurity, usePipelineAnalyticsSetTrackRelation, usePipelineAnalyticsStartStorageMigrationMove, usePipelineAnalyticsWipeAllAnalytics, usePipelineAnalyticsWipeObjectEmbeddings, usePipelineAnalyticsWriteRetrainArchive, usePipelineExecutorCacheFrameInPool, usePipelineExecutorClearDeviceOverrides, usePipelineExecutorClearModelBoundDeviceOverrides, usePipelineExecutorDeleteModel, usePipelineExecutorDownloadModel, usePipelineExecutorGetAddonModels, usePipelineExecutorGetAudioCapabilities, usePipelineExecutorGetAvailableEngines, usePipelineExecutorGetCapabilities, usePipelineExecutorGetDefaultSteps, usePipelineExecutorGetDetectionConfigSchema, usePipelineExecutorGetEffectiveTuning, usePipelineExecutorGetEngineProvisioning, usePipelineExecutorGetGlobalPipelineConfig, usePipelineExecutorGetGlobalSteps, usePipelineExecutorGetInferenceDeviceHealth, usePipelineExecutorGetOrchestratorConfigSchema, usePipelineExecutorGetReferenceAudio, usePipelineExecutorGetReferenceAudioFiles, usePipelineExecutorGetReferenceImage, usePipelineExecutorGetSchema, usePipelineExecutorGetSelectedEngine, usePipelineExecutorGetVideoPipelineSteps, usePipelineExecutorInferCached, usePipelineExecutorKillEngine, usePipelineExecutorListLoadedEngines, usePipelineExecutorListReferenceImages, usePipelineExecutorRearmInferenceDevice, usePipelineExecutorRunAudioTest, usePipelineExecutorRunPipeline, usePipelineExecutorRunPipelineBatch, usePipelineExecutorSetVideoPipelineSteps, usePipelineExecutorSpinEngine, usePipelineExecutorUncacheFrame, usePipelineExecutorValidatePipeline, usePipelineOrchestratorAbortNodeBenchmark, usePipelineOrchestratorApplyAutoTune, usePipelineOrchestratorApplyDeviceSettingsPatch, usePipelineOrchestratorAssignAudio, usePipelineOrchestratorAssignPipeline, usePipelineOrchestratorDeleteNodeBenchmarkResult, usePipelineOrchestratorGetAgentLoad, usePipelineOrchestratorGetAgentSettings, usePipelineOrchestratorGetAudioAssignment, usePipelineOrchestratorGetAudioAssignments, usePipelineOrchestratorGetAudioNodeLoad, usePipelineOrchestratorGetCameraMetrics, usePipelineOrchestratorGetCameraSettings, usePipelineOrchestratorGetCameraStatus, usePipelineOrchestratorGetCameraStatuses, usePipelineOrchestratorGetCameraStepOverrides, usePipelineOrchestratorGetCameraSwitches, usePipelineOrchestratorGetCapabilityBindings, usePipelineOrchestratorGetClusterModelChoice, usePipelineOrchestratorGetDeviceLiveContribution, usePipelineOrchestratorGetDeviceSettingsContribution, usePipelineOrchestratorGetEffectiveShares, usePipelineOrchestratorGetGlobalMetrics, usePipelineOrchestratorGetIngestOwner, usePipelineOrchestratorGetNodeBenchmarkCandidates, usePipelineOrchestratorGetNodeBenchmarkPreflight, usePipelineOrchestratorGetNodeBenchmarkStatus, usePipelineOrchestratorGetNodeInferenceDevices, usePipelineOrchestratorGetPipelineAssignment, usePipelineOrchestratorGetPipelineAssignments, usePipelineOrchestratorGetPipelineDevicePin, usePipelineOrchestratorGetPlacementStats, usePipelineOrchestratorGetReconcileFromDiskStatus, usePipelineOrchestratorListAgentSettings, usePipelineOrchestratorListNodeBenchmarkResults, usePipelineOrchestratorPauseForStorageMigration, usePipelineOrchestratorPlanAutoTune, usePipelineOrchestratorRebalance, usePipelineOrchestratorReconcileFromDisk, usePipelineOrchestratorRemoveAgentSettings, usePipelineOrchestratorRequestOccupancyLook, usePipelineOrchestratorResetNodePipelineDefaults, usePipelineOrchestratorResolvePipeline, usePipelineOrchestratorResumeForStorageMigration, usePipelineOrchestratorSetAgentCapabilities, usePipelineOrchestratorSetAgentDecodeLimits, usePipelineOrchestratorSetAgentInferenceDevices, usePipelineOrchestratorSetAgentMaxCameras, usePipelineOrchestratorSetAgentReachableHost, usePipelineOrchestratorSetCameraPipelineForAgent, usePipelineOrchestratorSetCameraSensors, usePipelineOrchestratorSetCameraStepOverride, usePipelineOrchestratorSetCameraStepSettings, usePipelineOrchestratorSetCameraStepToggle, usePipelineOrchestratorSetCameraSwitch, usePipelineOrchestratorSetCapabilityBinding, usePipelineOrchestratorSetLoadBalancingShares, usePipelineOrchestratorSetPipelineDevicePin, usePipelineOrchestratorSetShareSource, usePipelineOrchestratorStartNodeBenchmark, usePipelineOrchestratorSuggestShares, usePipelineOrchestratorUnassignAudio, usePipelineOrchestratorUnassignPipeline, usePipelineRunnerAbortNodeBench, usePipelineRunnerAttachCamera, usePipelineRunnerDetachCamera, usePipelineRunnerGetAllCameraMetrics, usePipelineRunnerGetCameraMetrics, usePipelineRunnerGetLocalCameras, usePipelineRunnerGetLocalLoad, usePipelineRunnerGetLocalMetrics, usePipelineRunnerGetNativeCrop, usePipelineRunnerGetNodeBenchStatus, usePipelineRunnerGetParkedTrackFrame, usePipelineRunnerParkTrackFrame, usePipelineRunnerReleaseParkedTrackFrames, usePipelineRunnerReportMotion, usePipelineRunnerRequestOccupancyBurst, usePipelineRunnerRunDetailSubtree, usePipelineRunnerRunStatelessStep, usePipelineRunnerStartNodeBench, 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, useStreamBrokerRegenerateRtspToken, useStreamBrokerReleaseEgressTranscode, useStreamBrokerReleaseStreamWithCodec, useStreamBrokerRenderPreBufferClip, useStreamBrokerReportStreamSignal, useStreamBrokerRestartProfile, useStreamBrokerRetractCameraStream, useStreamBrokerSetDeviceAudioMute, useStreamBrokerSetPreBufferDuration, useStreamBrokerSetRtspEnabled, useStreamBrokerSubscribeAudioChunks, useStreamBrokerUnassignProfile, useStreamBrokerUnsubscribeAudioChunks, 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, useUserManagementFindUserByExternalIdentity, useUserManagementGetTotpStatus, useUserManagementLinkExternalIdentity, useUserManagementListApiKeys, useUserManagementListExternalIdentities, useUserManagementListOauthSessions, useUserManagementListScopedTokens, useUserManagementListUsers, useUserManagementOauthExchangeCode, useUserManagementOauthIssueCode, useUserManagementOauthRefresh, useUserManagementOauthVerifyAccessToken, useUserManagementResetPassword, useUserManagementRevokeApiKey, useUserManagementRevokeOauthSession, useUserManagementRevokeScopedToken, useUserManagementSetUserScopes, useUserManagementSetupTotp, useUserManagementUnlinkExternalIdentity, 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, withUseRootModel, writeStoredGroupBy };