@camstack/ui-library 1.2.331 → 1.2.333

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.cjs CHANGED
@@ -40,6 +40,7 @@ let react_dom_client = require("react-dom/client");
40
40
  react_dom_client = __toESM(react_dom_client, 1);
41
41
  let _trpc_client = require("@trpc/client");
42
42
  _trpc_client = __toESM(_trpc_client, 1);
43
+ let _camstack_types_client = require("@camstack/types/client");
43
44
  let _camstack_sdk = require("@camstack/sdk");
44
45
  let zod = require("zod");
45
46
  //#region src/contexts/ui-text.tsx
@@ -4706,6 +4707,40 @@ var VolumeX = require_createLucideIcon.createLucideIcon("volume-x", [
4706
4707
  * This source code is licensed under the ISC license.
4707
4708
  * See the LICENSE file in the root directory of this source tree.
4708
4709
  */
4710
+ var WashingMachine = require_createLucideIcon.createLucideIcon("washing-machine", [
4711
+ ["path", {
4712
+ d: "M3 6h3",
4713
+ key: "155dbl"
4714
+ }],
4715
+ ["path", {
4716
+ d: "M17 6h.01",
4717
+ key: "e2y6kg"
4718
+ }],
4719
+ ["rect", {
4720
+ width: "18",
4721
+ height: "20",
4722
+ x: "3",
4723
+ y: "2",
4724
+ rx: "2",
4725
+ key: "od3kk9"
4726
+ }],
4727
+ ["circle", {
4728
+ cx: "12",
4729
+ cy: "13",
4730
+ r: "5",
4731
+ key: "nlbqau"
4732
+ }],
4733
+ ["path", {
4734
+ d: "M12 18a2.5 2.5 0 0 0 0-5 2.5 2.5 0 0 1 0-5",
4735
+ key: "17lach"
4736
+ }]
4737
+ ]);
4738
+ /**
4739
+ * @license lucide-react v0.576.0 - ISC
4740
+ *
4741
+ * This source code is licensed under the ISC license.
4742
+ * See the LICENSE file in the root directory of this source tree.
4743
+ */
4709
4744
  var Waves = require_createLucideIcon.createLucideIcon("waves", [
4710
4745
  ["path", {
4711
4746
  d: "M2 6c.6.5 1.2 1 2.5 1C7 7 7 5 9.5 5c2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1",
@@ -5369,6 +5404,16 @@ var DEVICE_TYPE_META = {
5369
5404
  icon: Waves,
5370
5405
  labelKey: "uiLibrary.deviceMeta.type.dishwasher",
5371
5406
  labelFallback: "Dishwasher"
5407
+ },
5408
+ [_camstack_types.DeviceType.Washer]: {
5409
+ icon: WashingMachine,
5410
+ labelKey: "uiLibrary.deviceMeta.type.washer",
5411
+ labelFallback: "Washing machine"
5412
+ },
5413
+ [_camstack_types.DeviceType.Dryer]: {
5414
+ icon: Wind,
5415
+ labelKey: "uiLibrary.deviceMeta.type.dryer",
5416
+ labelFallback: "Dryer"
5372
5417
  }
5373
5418
  };
5374
5419
  var DEVICE_ROLE_META = {
@@ -5850,7 +5895,7 @@ function lucideExportName(key) {
5850
5895
  function loadIconGlyphs() {
5851
5896
  if (fullTable !== void 0) return Promise.resolve();
5852
5897
  if (failure$1 !== void 0) return Promise.reject(failure$1);
5853
- pending ??= Promise.resolve().then(() => require("./icons-pqV1UOV_.cjs")).then((module) => {
5898
+ pending ??= Promise.resolve().then(() => require("./icons-CWmwnulO.cjs")).then((module) => {
5854
5899
  fullTable = module;
5855
5900
  publish();
5856
5901
  }, (error) => {
@@ -14504,6 +14549,9 @@ function AccountSignInField({ field, values, disabled, onAction, onChangeMany })
14504
14549
  if (Object.keys(patch).length > 0) onChangeMany?.(patch);
14505
14550
  const nextChoices = choicesOf(result["choices"]);
14506
14551
  setChoices(nextChoices.length > 1 ? nextChoices : []);
14552
+ const picked = typeof result["gatewayId"] === "string" && result["gatewayId"].length > 0;
14553
+ const summary = typeof result["summary"] === "string" ? result["summary"] : null;
14554
+ if (summary !== null && !picked && nextChoices.length < 2) setError(summary);
14507
14555
  setPhase("idle");
14508
14556
  setPaste("");
14509
14557
  setAttemptId(null);
@@ -19624,6 +19672,1151 @@ function AutotrackSection({ deviceId }) {
19624
19672
  });
19625
19673
  }
19626
19674
  //#endregion
19675
+ //#region src/composites/device-name/device-fleet.tsx
19676
+ /**
19677
+ * One slim fleet, shared. A row reads its parent from this index — never a
19678
+ * `getDevice` per row (the Customize bounded-read rule). `null` is "not
19679
+ * answered yet": a label then keeps the stored name, so a list does not flash
19680
+ * `#id` while the fleet is in flight. An empty array is an answer.
19681
+ */
19682
+ var DeviceNameIndexContext = (0, react$1.createContext)(null);
19683
+ function useDeviceNameIndex() {
19684
+ return (0, react$1.useContext)(DeviceNameIndexContext);
19685
+ }
19686
+ function copyDevice(device) {
19687
+ const label = device.display?.label;
19688
+ const trimmed = label === void 0 || label === null ? "" : label.trim();
19689
+ return {
19690
+ id: device.id,
19691
+ name: device.name,
19692
+ type: device.type,
19693
+ stableId: device.stableId,
19694
+ role: device.role ?? null,
19695
+ parentDeviceId: device.parentDeviceId ?? null,
19696
+ location: device.location ?? null,
19697
+ ...trimmed.length > 0 ? { display: { label: trimmed } } : {},
19698
+ ...device.primaryChildEntityId !== void 0 ? { primaryChildEntityId: device.primaryChildEntityId } : {},
19699
+ ...device.userNamed === void 0 ? {} : { userNamed: device.userNamed }
19700
+ };
19701
+ }
19702
+ /** Build the parent/child maps once per fleet answer. */
19703
+ function indexDeviceNames(devices) {
19704
+ const byId = /* @__PURE__ */ new Map();
19705
+ const children = /* @__PURE__ */ new Map();
19706
+ for (const device of devices) {
19707
+ const fields = copyDevice(device);
19708
+ byId.set(fields.id, fields);
19709
+ const parentId = fields.parentDeviceId;
19710
+ if (parentId === null || parentId === void 0 || parentId === fields.id) continue;
19711
+ const list = children.get(parentId);
19712
+ if (list === void 0) children.set(parentId, [fields.id]);
19713
+ else list.push(fields.id);
19714
+ }
19715
+ return {
19716
+ byId,
19717
+ childIds: children
19718
+ };
19719
+ }
19720
+ function DeviceFleetProvider({ devices, children }) {
19721
+ const index = (0, react$1.useMemo)(() => devices === null ? null : indexDeviceNames(devices), [devices]);
19722
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceNameIndexContext.Provider, {
19723
+ value: index,
19724
+ children
19725
+ });
19726
+ }
19727
+ /**
19728
+ * The one `deviceManager.listAll` subscriber for names. Same query key the
19729
+ * pickers already use, so a second observer does not fetch a second fleet.
19730
+ */
19731
+ function DeviceFleetFromServer({ children }) {
19732
+ const query = useDeviceManagerListAll({ projection: "slim" });
19733
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceFleetProvider, {
19734
+ devices: query.data === void 0 ? null : query.data,
19735
+ children
19736
+ });
19737
+ }
19738
+ //#endregion
19739
+ //#region src/composites/device-name/format-device-name.ts
19740
+ /**
19741
+ * One display name for a device.
19742
+ *
19743
+ * Canonical copy: `packages/ui-library/src/composites/device-name/format-device-name.ts`.
19744
+ * The viewer mirrors this file; `scripts/check-device-name-in-sync.ts` diffs the two.
19745
+ * Noun strings stay outside — this file only decides which noun to ask for.
19746
+ */
19747
+ /** Middle dot used between a container and its noun, and before a room. */
19748
+ var DEVICE_NAME_JOIN = " · ";
19749
+ var DEVICE_NAME_FORMS = [
19750
+ "compact",
19751
+ "full",
19752
+ "nested",
19753
+ "with-location"
19754
+ ];
19755
+ var PARENT_SEPARATORS = [" — ", " "];
19756
+ /**
19757
+ * Readings that are never what a container is. Matches the comment on
19758
+ * `CONTAINER_CHILD_ROLE_PRIORITY`: battery, temperature, humidity, illuminance,
19759
+ * signal, last-seen, plus numeric and enum. `update` is the same kind of row.
19760
+ */
19761
+ var DIAGNOSTIC_ROLES = new Set([
19762
+ "battery-sensor",
19763
+ "temperature-sensor",
19764
+ "humidity-sensor",
19765
+ "ambient-light-sensor",
19766
+ "connectivity-sensor",
19767
+ "datetime-sensor",
19768
+ "numeric-sensor",
19769
+ "enum-sensor"
19770
+ ]);
19771
+ /** Hyphen, underscore and whitespace fold together — the namesake comparison. */
19772
+ function foldDeviceName(value) {
19773
+ return value.toLowerCase().replace(/[-_\s]+/g, " ").trim();
19774
+ }
19775
+ function sameDeviceName(left, right) {
19776
+ return foldDeviceName(left) === foldDeviceName(right);
19777
+ }
19778
+ /**
19779
+ * Drop a parent prefix. `" — "` before a space, case-insensitive, so a provider
19780
+ * that capitalised the parent differently still strips, and an em-dash child
19781
+ * does not keep a dangling dash.
19782
+ */
19783
+ function stripDeviceParentPrefix(name, parentName) {
19784
+ if (parentName.length === 0) return name;
19785
+ const nameFold = name.toLowerCase();
19786
+ const parentFold = parentName.toLowerCase();
19787
+ for (const separator of PARENT_SEPARATORS) {
19788
+ const prefix = `${parentFold}${separator}`;
19789
+ if (!nameFold.startsWith(prefix)) continue;
19790
+ return name.slice(prefix.length).trim();
19791
+ }
19792
+ return name;
19793
+ }
19794
+ function roleOf(device) {
19795
+ const role = device.role;
19796
+ if (role === void 0 || role === null || role.length === 0) return null;
19797
+ return role;
19798
+ }
19799
+ function isDiagnostic(device) {
19800
+ if (device.type === "update") return true;
19801
+ const role = roleOf(device);
19802
+ return role !== null && DIAGNOSTIC_ROLES.has(role);
19803
+ }
19804
+ function displayLabel(device) {
19805
+ const label = device.display?.label;
19806
+ if (label === void 0 || label === null) return "";
19807
+ return label.trim();
19808
+ }
19809
+ function nounOf(device, parent, nouns) {
19810
+ const caption = displayLabel(device);
19811
+ if (caption.length > 0) return caption;
19812
+ const stripped = stripDeviceParentPrefix(device.name, parent.name);
19813
+ const shown = stripped.length > 0 ? stripped : device.name;
19814
+ if (device.userNamed === true) return shown;
19815
+ if (stripped.length === 0 || sameDeviceName(stripped, parent.name) || nouns.isStockPhrase(shown, roleOf(device), device.type)) return nouns.shortNoun(roleOf(device), device.type);
19816
+ return shown;
19817
+ }
19818
+ function foldsIntoParent(device, parent, noun) {
19819
+ if (sameDeviceName(noun, parent.name)) return true;
19820
+ if (displayLabel(device).length > 0 || device.userNamed === true) return false;
19821
+ return sameDeviceName(stripDeviceParentPrefix(device.name, parent.name), parent.name);
19822
+ }
19823
+ function meaningfulChildCount(siblings) {
19824
+ let count = 0;
19825
+ for (const sibling of siblings) if (!isDiagnostic(sibling)) count += 1;
19826
+ return count;
19827
+ }
19828
+ function isContainerPrimary(device, parent, siblings) {
19829
+ const picked = (0, _camstack_types_client.resolveContainerPrimaryChild)(siblings.map((sibling) => {
19830
+ const role = roleOf(sibling);
19831
+ return {
19832
+ id: sibling.id,
19833
+ stableId: sibling.stableId,
19834
+ type: sibling.type,
19835
+ name: sibling.name,
19836
+ ...role === null ? {} : { role }
19837
+ };
19838
+ }), parent.primaryChildEntityId, parent.name);
19839
+ return picked !== null && picked.id === device.id;
19840
+ }
19841
+ function childLine(device, parent, siblings, nouns, form) {
19842
+ const noun = nounOf(device, parent, nouns);
19843
+ if (foldsIntoParent(device, parent, noun)) return parent.name;
19844
+ if (form === "compact" && parent.type === "container" && isContainerPrimary(device, parent, siblings) && meaningfulChildCount(siblings) === 1) return parent.name;
19845
+ return `${parent.name}${DEVICE_NAME_JOIN}${noun}`;
19846
+ }
19847
+ function joinLocation(label, location) {
19848
+ if (location === void 0 || location === null) return label;
19849
+ const room = location.trim();
19850
+ if (room.length === 0) return label;
19851
+ if (label.split(" · ").some((segment) => sameDeviceName(segment, room))) return label;
19852
+ return `${label}${DEVICE_NAME_JOIN}${room}`;
19853
+ }
19854
+ function hasParent(device, parent) {
19855
+ if (parent === null) return false;
19856
+ const parentId = device.parentDeviceId;
19857
+ return parentId !== null && parentId !== void 0;
19858
+ }
19859
+ /**
19860
+ * The label for `form`. A top-level device keeps its stored name. A child uses
19861
+ * the noun: `compact` may stand in for the container when this child is what
19862
+ * the container is, `full` always names both unless they fold together,
19863
+ * `nested` is the noun alone, `with-location` is `compact` plus the room.
19864
+ */
19865
+ function formatDeviceName(device, form, context, nouns) {
19866
+ if (!hasParent(device, context.parent)) return form === "with-location" ? joinLocation(device.name, device.location) : device.name;
19867
+ if (form === "nested") return nounOf(device, context.parent, nouns);
19868
+ const line = childLine(device, context.parent, context.siblings, nouns, form === "full" ? "full" : "compact");
19869
+ return form === "with-location" ? joinLocation(line, device.location) : line;
19870
+ }
19871
+ //#endregion
19872
+ //#region src/composites/device-name/device-name-nouns.ts
19873
+ /**
19874
+ * Short nouns and the stock words they replace.
19875
+ *
19876
+ * Long labels are the existing role and type names, both locales, so a stored
19877
+ * "Motion sensor" and a stored "Sensore di movimento" are the same stock word.
19878
+ * The short noun is what a list shows. Where the long label is already one
19879
+ * word it is the short noun. Aliases are provider stock words that are not the
19880
+ * label ("Illumination", "Last seen").
19881
+ */
19882
+ var DEVICE_ROLE_NOUNS = [
19883
+ {
19884
+ id: "siren",
19885
+ key: "uiLibrary.deviceMeta.noun.role.siren",
19886
+ en: "Siren",
19887
+ it: "Sirena",
19888
+ shortEn: "Siren",
19889
+ shortIt: "Sirena",
19890
+ aliases: []
19891
+ },
19892
+ {
19893
+ id: "floodlight",
19894
+ key: "uiLibrary.deviceMeta.noun.role.floodlight",
19895
+ en: "Floodlight",
19896
+ it: "Faro",
19897
+ shortEn: "Floodlight",
19898
+ shortIt: "Faro",
19899
+ aliases: []
19900
+ },
19901
+ {
19902
+ id: "spotlight",
19903
+ key: "uiLibrary.deviceMeta.noun.role.spotlight",
19904
+ en: "Spotlight",
19905
+ it: "Faretto",
19906
+ shortEn: "Spotlight",
19907
+ shortIt: "Faretto",
19908
+ aliases: []
19909
+ },
19910
+ {
19911
+ id: "pir-sensor",
19912
+ key: "uiLibrary.deviceMeta.noun.role.pirSensor",
19913
+ en: "PIR sensor",
19914
+ it: "Sensore PIR",
19915
+ shortEn: "PIR",
19916
+ shortIt: "PIR",
19917
+ aliases: []
19918
+ },
19919
+ {
19920
+ id: "chime",
19921
+ key: "uiLibrary.deviceMeta.noun.role.chime",
19922
+ en: "Chime",
19923
+ it: "Campanello",
19924
+ shortEn: "Chime",
19925
+ shortIt: "Campanello",
19926
+ aliases: []
19927
+ },
19928
+ {
19929
+ id: "autotrack",
19930
+ key: "uiLibrary.deviceMeta.noun.role.autotrack",
19931
+ en: "Auto-track",
19932
+ it: "Auto-tracking",
19933
+ shortEn: "Auto-track",
19934
+ shortIt: "Auto-tracking",
19935
+ aliases: []
19936
+ },
19937
+ {
19938
+ id: "nightvision",
19939
+ key: "uiLibrary.deviceMeta.noun.role.nightvision",
19940
+ en: "Night vision",
19941
+ it: "Visione notturna",
19942
+ shortEn: "Night",
19943
+ shortIt: "Notturna",
19944
+ aliases: []
19945
+ },
19946
+ {
19947
+ id: "privacy-mask",
19948
+ key: "uiLibrary.deviceMeta.noun.role.privacyMask",
19949
+ en: "Privacy mask",
19950
+ it: "Maschera privacy",
19951
+ shortEn: "Privacy",
19952
+ shortIt: "Privacy",
19953
+ aliases: []
19954
+ },
19955
+ {
19956
+ id: "doorbell",
19957
+ key: "uiLibrary.deviceMeta.noun.role.doorbell",
19958
+ en: "Doorbell",
19959
+ it: "Campanello video",
19960
+ shortEn: "Doorbell",
19961
+ shortIt: "Campanello video",
19962
+ aliases: []
19963
+ },
19964
+ {
19965
+ id: "cover-blind",
19966
+ key: "uiLibrary.deviceMeta.noun.role.coverBlind",
19967
+ en: "Blind",
19968
+ it: "Tapparella",
19969
+ shortEn: "Blind",
19970
+ shortIt: "Tapparella",
19971
+ aliases: []
19972
+ },
19973
+ {
19974
+ id: "cover-shutter",
19975
+ key: "uiLibrary.deviceMeta.noun.role.coverShutter",
19976
+ en: "Shutter",
19977
+ it: "Persiana",
19978
+ shortEn: "Shutter",
19979
+ shortIt: "Persiana",
19980
+ aliases: []
19981
+ },
19982
+ {
19983
+ id: "cover-awning",
19984
+ key: "uiLibrary.deviceMeta.noun.role.coverAwning",
19985
+ en: "Awning",
19986
+ it: "Tenda da sole",
19987
+ shortEn: "Awning",
19988
+ shortIt: "Tenda da sole",
19989
+ aliases: []
19990
+ },
19991
+ {
19992
+ id: "cover-curtain",
19993
+ key: "uiLibrary.deviceMeta.noun.role.coverCurtain",
19994
+ en: "Curtain",
19995
+ it: "Tenda",
19996
+ shortEn: "Curtain",
19997
+ shortIt: "Tenda",
19998
+ aliases: []
19999
+ },
20000
+ {
20001
+ id: "cover-garage",
20002
+ key: "uiLibrary.deviceMeta.noun.role.coverGarage",
20003
+ en: "Garage door",
20004
+ it: "Porta del garage",
20005
+ shortEn: "Garage",
20006
+ shortIt: "Garage",
20007
+ aliases: []
20008
+ },
20009
+ {
20010
+ id: "cover-gate",
20011
+ key: "uiLibrary.deviceMeta.noun.role.coverGate",
20012
+ en: "Gate",
20013
+ it: "Cancello",
20014
+ shortEn: "Gate",
20015
+ shortIt: "Cancello",
20016
+ aliases: []
20017
+ },
20018
+ {
20019
+ id: "cover-door",
20020
+ key: "uiLibrary.deviceMeta.noun.role.coverDoor",
20021
+ en: "Door",
20022
+ it: "Porta",
20023
+ shortEn: "Door",
20024
+ shortIt: "Porta",
20025
+ aliases: []
20026
+ },
20027
+ {
20028
+ id: "cover-window",
20029
+ key: "uiLibrary.deviceMeta.noun.role.coverWindow",
20030
+ en: "Window",
20031
+ it: "Finestra",
20032
+ shortEn: "Window",
20033
+ shortIt: "Finestra",
20034
+ aliases: []
20035
+ },
20036
+ {
20037
+ id: "binary-helper",
20038
+ key: "uiLibrary.deviceMeta.noun.role.binaryHelper",
20039
+ en: "Binary helper",
20040
+ it: "Helper binario",
20041
+ shortEn: "Helper",
20042
+ shortIt: "Helper",
20043
+ aliases: []
20044
+ },
20045
+ {
20046
+ id: "outlet",
20047
+ key: "uiLibrary.deviceMeta.noun.role.outlet",
20048
+ en: "Outlet",
20049
+ it: "Presa",
20050
+ shortEn: "Outlet",
20051
+ shortIt: "Presa",
20052
+ aliases: []
20053
+ },
20054
+ {
20055
+ id: "motion-sensor",
20056
+ key: "uiLibrary.deviceMeta.noun.role.motionSensor",
20057
+ en: "Motion sensor",
20058
+ it: "Sensore di movimento",
20059
+ shortEn: "Motion",
20060
+ shortIt: "Movimento",
20061
+ aliases: []
20062
+ },
20063
+ {
20064
+ id: "contact-sensor",
20065
+ key: "uiLibrary.deviceMeta.noun.role.contactSensor",
20066
+ en: "Contact sensor",
20067
+ it: "Sensore di contatto",
20068
+ shortEn: "Contact",
20069
+ shortIt: "Contatto",
20070
+ aliases: []
20071
+ },
20072
+ {
20073
+ id: "leak-sensor",
20074
+ key: "uiLibrary.deviceMeta.noun.role.leakSensor",
20075
+ en: "Leak sensor",
20076
+ it: "Sensore di perdite",
20077
+ shortEn: "Leak",
20078
+ shortIt: "Perdita",
20079
+ aliases: []
20080
+ },
20081
+ {
20082
+ id: "smoke-sensor",
20083
+ key: "uiLibrary.deviceMeta.noun.role.smokeSensor",
20084
+ en: "Smoke sensor",
20085
+ it: "Sensore di fumo",
20086
+ shortEn: "Smoke",
20087
+ shortIt: "Fumo",
20088
+ aliases: []
20089
+ },
20090
+ {
20091
+ id: "co-sensor",
20092
+ key: "uiLibrary.deviceMeta.noun.role.cOSensor",
20093
+ en: "CO sensor",
20094
+ it: "Sensore di CO",
20095
+ shortEn: "CO",
20096
+ shortIt: "CO",
20097
+ aliases: []
20098
+ },
20099
+ {
20100
+ id: "gas-sensor",
20101
+ key: "uiLibrary.deviceMeta.noun.role.gasSensor",
20102
+ en: "Gas sensor",
20103
+ it: "Sensore di gas",
20104
+ shortEn: "Gas",
20105
+ shortIt: "Gas",
20106
+ aliases: []
20107
+ },
20108
+ {
20109
+ id: "tamper-sensor",
20110
+ key: "uiLibrary.deviceMeta.noun.role.tamperSensor",
20111
+ en: "Tamper sensor",
20112
+ it: "Sensore antimanomissione",
20113
+ shortEn: "Tamper",
20114
+ shortIt: "Manomissione",
20115
+ aliases: []
20116
+ },
20117
+ {
20118
+ id: "vibration-sensor",
20119
+ key: "uiLibrary.deviceMeta.noun.role.vibrationSensor",
20120
+ en: "Vibration sensor",
20121
+ it: "Sensore di vibrazione",
20122
+ shortEn: "Vibration",
20123
+ shortIt: "Vibrazione",
20124
+ aliases: []
20125
+ },
20126
+ {
20127
+ id: "connectivity-sensor",
20128
+ key: "uiLibrary.deviceMeta.noun.role.connectivitySensor",
20129
+ en: "Connectivity",
20130
+ it: "Connettività",
20131
+ shortEn: "Connectivity",
20132
+ shortIt: "Connettività",
20133
+ aliases: []
20134
+ },
20135
+ {
20136
+ id: "sound-sensor",
20137
+ key: "uiLibrary.deviceMeta.noun.role.soundSensor",
20138
+ en: "Sound sensor",
20139
+ it: "Sensore acustico",
20140
+ shortEn: "Sound",
20141
+ shortIt: "Suono",
20142
+ aliases: []
20143
+ },
20144
+ {
20145
+ id: "binary-sensor",
20146
+ key: "uiLibrary.deviceMeta.noun.role.binarySensor",
20147
+ en: "Binary sensor",
20148
+ it: "Sensore binario",
20149
+ shortEn: "Binary",
20150
+ shortIt: "Binario",
20151
+ aliases: []
20152
+ },
20153
+ {
20154
+ id: "temperature-sensor",
20155
+ key: "uiLibrary.deviceMeta.noun.role.temperatureSensor",
20156
+ en: "Temperature",
20157
+ it: "Temperatura",
20158
+ shortEn: "Temperature",
20159
+ shortIt: "Temperatura",
20160
+ aliases: []
20161
+ },
20162
+ {
20163
+ id: "humidity-sensor",
20164
+ key: "uiLibrary.deviceMeta.noun.role.humiditySensor",
20165
+ en: "Humidity",
20166
+ it: "Umidità",
20167
+ shortEn: "Humidity",
20168
+ shortIt: "Umidità",
20169
+ aliases: []
20170
+ },
20171
+ {
20172
+ id: "ambient-light-sensor",
20173
+ key: "uiLibrary.deviceMeta.noun.role.ambientLightSensor",
20174
+ en: "Ambient light",
20175
+ it: "Luce ambientale",
20176
+ shortEn: "Light",
20177
+ shortIt: "Luce",
20178
+ aliases: ["Illumination"]
20179
+ },
20180
+ {
20181
+ id: "pressure-sensor",
20182
+ key: "uiLibrary.deviceMeta.noun.role.pressureSensor",
20183
+ en: "Pressure",
20184
+ it: "Pressione",
20185
+ shortEn: "Pressure",
20186
+ shortIt: "Pressione",
20187
+ aliases: []
20188
+ },
20189
+ {
20190
+ id: "wind-sensor",
20191
+ key: "uiLibrary.deviceMeta.noun.role.windSensor",
20192
+ en: "Wind",
20193
+ it: "Vento",
20194
+ shortEn: "Wind",
20195
+ shortIt: "Vento",
20196
+ aliases: []
20197
+ },
20198
+ {
20199
+ id: "rain-sensor",
20200
+ key: "uiLibrary.deviceMeta.noun.role.rainSensor",
20201
+ en: "Rain",
20202
+ it: "Pioggia",
20203
+ shortEn: "Rain",
20204
+ shortIt: "Pioggia",
20205
+ aliases: []
20206
+ },
20207
+ {
20208
+ id: "uv-sensor",
20209
+ key: "uiLibrary.deviceMeta.noun.role.uvSensor",
20210
+ en: "UV index",
20211
+ it: "Indice UV",
20212
+ shortEn: "UV index",
20213
+ shortIt: "Indice UV",
20214
+ aliases: []
20215
+ },
20216
+ {
20217
+ id: "solar-radiation-sensor",
20218
+ key: "uiLibrary.deviceMeta.noun.role.solarRadiationSensor",
20219
+ en: "Solar radiation",
20220
+ it: "Radiazione solare",
20221
+ shortEn: "Solar radiation",
20222
+ shortIt: "Radiazione solare",
20223
+ aliases: []
20224
+ },
20225
+ {
20226
+ id: "soil-moisture-sensor",
20227
+ key: "uiLibrary.deviceMeta.noun.role.soilMoistureSensor",
20228
+ en: "Soil moisture",
20229
+ it: "Umidità del suolo",
20230
+ shortEn: "Soil moisture",
20231
+ shortIt: "Umidità del suolo",
20232
+ aliases: []
20233
+ },
20234
+ {
20235
+ id: "power-sensor",
20236
+ key: "uiLibrary.deviceMeta.noun.role.powerSensor",
20237
+ en: "Power",
20238
+ it: "Potenza",
20239
+ shortEn: "Power",
20240
+ shortIt: "Potenza",
20241
+ aliases: []
20242
+ },
20243
+ {
20244
+ id: "energy-sensor",
20245
+ key: "uiLibrary.deviceMeta.noun.role.energySensor",
20246
+ en: "Energy",
20247
+ it: "Energia",
20248
+ shortEn: "Energy",
20249
+ shortIt: "Energia",
20250
+ aliases: []
20251
+ },
20252
+ {
20253
+ id: "voltage-sensor",
20254
+ key: "uiLibrary.deviceMeta.noun.role.voltageSensor",
20255
+ en: "Voltage",
20256
+ it: "Tensione",
20257
+ shortEn: "Voltage",
20258
+ shortIt: "Tensione",
20259
+ aliases: []
20260
+ },
20261
+ {
20262
+ id: "current-sensor",
20263
+ key: "uiLibrary.deviceMeta.noun.role.currentSensor",
20264
+ en: "Current",
20265
+ it: "Corrente",
20266
+ shortEn: "Current",
20267
+ shortIt: "Corrente",
20268
+ aliases: []
20269
+ },
20270
+ {
20271
+ id: "air-quality-sensor",
20272
+ key: "uiLibrary.deviceMeta.noun.role.airQualitySensor",
20273
+ en: "Air quality",
20274
+ it: "Qualità dell’aria",
20275
+ shortEn: "Air quality",
20276
+ shortIt: "Qualità dell’aria",
20277
+ aliases: []
20278
+ },
20279
+ {
20280
+ id: "battery-sensor",
20281
+ key: "uiLibrary.deviceMeta.noun.role.batterySensor",
20282
+ en: "Battery",
20283
+ it: "Batteria",
20284
+ shortEn: "Battery",
20285
+ shortIt: "Batteria",
20286
+ aliases: []
20287
+ },
20288
+ {
20289
+ id: "numeric-sensor",
20290
+ key: "uiLibrary.deviceMeta.noun.role.numericSensor",
20291
+ en: "Numeric sensor",
20292
+ it: "Sensore numerico",
20293
+ shortEn: "Value",
20294
+ shortIt: "Numerico",
20295
+ aliases: []
20296
+ },
20297
+ {
20298
+ id: "enum-sensor",
20299
+ key: "uiLibrary.deviceMeta.noun.role.enumSensor",
20300
+ en: "Enum sensor",
20301
+ it: "Sensore a stati",
20302
+ shortEn: "State",
20303
+ shortIt: "Stato",
20304
+ aliases: []
20305
+ },
20306
+ {
20307
+ id: "datetime-sensor",
20308
+ key: "uiLibrary.deviceMeta.noun.role.dateTimeSensor",
20309
+ en: "Date / time",
20310
+ it: "Data / ora",
20311
+ shortEn: "Time",
20312
+ shortIt: "Ora",
20313
+ aliases: ["Last seen"]
20314
+ },
20315
+ {
20316
+ id: "generic-sensor",
20317
+ key: "uiLibrary.deviceMeta.noun.role.genericSensor",
20318
+ en: "Sensor",
20319
+ it: "Sensore",
20320
+ shortEn: "Sensor",
20321
+ shortIt: "Sensore",
20322
+ aliases: []
20323
+ },
20324
+ {
20325
+ id: "numeric-control",
20326
+ key: "uiLibrary.deviceMeta.noun.role.numericControl",
20327
+ en: "Numeric control",
20328
+ it: "Controllo numerico",
20329
+ shortEn: "Number",
20330
+ shortIt: "Numero",
20331
+ aliases: []
20332
+ },
20333
+ {
20334
+ id: "select-control",
20335
+ key: "uiLibrary.deviceMeta.noun.role.selectControl",
20336
+ en: "Select control",
20337
+ it: "Controllo a selezione",
20338
+ shortEn: "Select",
20339
+ shortIt: "Selezione",
20340
+ aliases: []
20341
+ },
20342
+ {
20343
+ id: "text-control",
20344
+ key: "uiLibrary.deviceMeta.noun.role.textControl",
20345
+ en: "Text control",
20346
+ it: "Controllo testo",
20347
+ shortEn: "Text",
20348
+ shortIt: "Testo",
20349
+ aliases: []
20350
+ },
20351
+ {
20352
+ id: "datetime-control",
20353
+ key: "uiLibrary.deviceMeta.noun.role.dateTimeControl",
20354
+ en: "Date / time control",
20355
+ it: "Controllo data / ora",
20356
+ shortEn: "Time",
20357
+ shortIt: "Ora",
20358
+ aliases: []
20359
+ },
20360
+ {
20361
+ id: "mobile-push-notifier",
20362
+ key: "uiLibrary.deviceMeta.noun.role.mobilePushNotifier",
20363
+ en: "Mobile push",
20364
+ it: "Push su mobile",
20365
+ shortEn: "Push",
20366
+ shortIt: "Push",
20367
+ aliases: []
20368
+ },
20369
+ {
20370
+ id: "messaging-notifier",
20371
+ key: "uiLibrary.deviceMeta.noun.role.messagingNotifier",
20372
+ en: "Messaging",
20373
+ it: "Messaggistica",
20374
+ shortEn: "Messaging",
20375
+ shortIt: "Messaggistica",
20376
+ aliases: []
20377
+ },
20378
+ {
20379
+ id: "email-notifier",
20380
+ key: "uiLibrary.deviceMeta.noun.role.emailNotifier",
20381
+ en: "Email",
20382
+ it: "Email",
20383
+ shortEn: "Email",
20384
+ shortIt: "Email",
20385
+ aliases: []
20386
+ },
20387
+ {
20388
+ id: "generic-notifier",
20389
+ key: "uiLibrary.deviceMeta.noun.role.genericNotifier",
20390
+ en: "Notifier",
20391
+ it: "Notificatore",
20392
+ shortEn: "Notifier",
20393
+ shortIt: "Notificatore",
20394
+ aliases: []
20395
+ }
20396
+ ];
20397
+ var DEVICE_TYPE_NOUNS = [
20398
+ {
20399
+ id: "camera",
20400
+ key: "uiLibrary.deviceMeta.noun.type.camera",
20401
+ en: "Camera",
20402
+ it: "Telecamera",
20403
+ shortEn: "Camera",
20404
+ shortIt: "Telecamera",
20405
+ aliases: []
20406
+ },
20407
+ {
20408
+ id: "hub",
20409
+ key: "uiLibrary.deviceMeta.noun.type.hub",
20410
+ en: "Hub",
20411
+ it: "Hub",
20412
+ shortEn: "Hub",
20413
+ shortIt: "Hub",
20414
+ aliases: []
20415
+ },
20416
+ {
20417
+ id: "light",
20418
+ key: "uiLibrary.deviceMeta.noun.type.light",
20419
+ en: "Light",
20420
+ it: "Luce",
20421
+ shortEn: "Light",
20422
+ shortIt: "Luce",
20423
+ aliases: []
20424
+ },
20425
+ {
20426
+ id: "siren",
20427
+ key: "uiLibrary.deviceMeta.noun.type.siren",
20428
+ en: "Siren",
20429
+ it: "Sirena",
20430
+ shortEn: "Siren",
20431
+ shortIt: "Sirena",
20432
+ aliases: []
20433
+ },
20434
+ {
20435
+ id: "switch",
20436
+ key: "uiLibrary.deviceMeta.noun.type.switch",
20437
+ en: "Switch",
20438
+ it: "Interruttore",
20439
+ shortEn: "Switch",
20440
+ shortIt: "Interruttore",
20441
+ aliases: []
20442
+ },
20443
+ {
20444
+ id: "sensor",
20445
+ key: "uiLibrary.deviceMeta.noun.type.sensor",
20446
+ en: "Sensor",
20447
+ it: "Sensore",
20448
+ shortEn: "Sensor",
20449
+ shortIt: "Sensore",
20450
+ aliases: []
20451
+ },
20452
+ {
20453
+ id: "thermostat",
20454
+ key: "uiLibrary.deviceMeta.noun.type.thermostat",
20455
+ en: "Thermostat",
20456
+ it: "Termostato",
20457
+ shortEn: "Thermostat",
20458
+ shortIt: "Termostato",
20459
+ aliases: []
20460
+ },
20461
+ {
20462
+ id: "climate",
20463
+ key: "uiLibrary.deviceMeta.noun.type.climate",
20464
+ en: "Air conditioner",
20465
+ it: "Condizionatore",
20466
+ shortEn: "Climate",
20467
+ shortIt: "Condizionatore",
20468
+ aliases: []
20469
+ },
20470
+ {
20471
+ id: "button",
20472
+ key: "uiLibrary.deviceMeta.noun.type.button",
20473
+ en: "Button",
20474
+ it: "Pulsante",
20475
+ shortEn: "Button",
20476
+ shortIt: "Pulsante",
20477
+ aliases: []
20478
+ },
20479
+ {
20480
+ id: "event-emitter",
20481
+ key: "uiLibrary.deviceMeta.noun.type.eventEmitter",
20482
+ en: "Event Emitter",
20483
+ it: "Emettitore di eventi",
20484
+ shortEn: "Event",
20485
+ shortIt: "Evento",
20486
+ aliases: []
20487
+ },
20488
+ {
20489
+ id: "update",
20490
+ key: "uiLibrary.deviceMeta.noun.type.update",
20491
+ en: "Update",
20492
+ it: "Aggiornamento",
20493
+ shortEn: "Update",
20494
+ shortIt: "Aggiornamento",
20495
+ aliases: []
20496
+ },
20497
+ {
20498
+ id: "generic",
20499
+ key: "uiLibrary.deviceMeta.noun.type.generic",
20500
+ en: "Generic",
20501
+ it: "Generico",
20502
+ shortEn: "Generic",
20503
+ shortIt: "Generico",
20504
+ aliases: []
20505
+ },
20506
+ {
20507
+ id: "notifier",
20508
+ key: "uiLibrary.deviceMeta.noun.type.notifier",
20509
+ en: "Notifier",
20510
+ it: "Notificatore",
20511
+ shortEn: "Notifier",
20512
+ shortIt: "Notificatore",
20513
+ aliases: []
20514
+ },
20515
+ {
20516
+ id: "script",
20517
+ key: "uiLibrary.deviceMeta.noun.type.script",
20518
+ en: "Script",
20519
+ it: "Script",
20520
+ shortEn: "Script",
20521
+ shortIt: "Script",
20522
+ aliases: []
20523
+ },
20524
+ {
20525
+ id: "automation",
20526
+ key: "uiLibrary.deviceMeta.noun.type.automation",
20527
+ en: "Automation",
20528
+ it: "Automazione",
20529
+ shortEn: "Automation",
20530
+ shortIt: "Automazione",
20531
+ aliases: []
20532
+ },
20533
+ {
20534
+ id: "lock",
20535
+ key: "uiLibrary.deviceMeta.noun.type.lock",
20536
+ en: "Lock",
20537
+ it: "Serratura",
20538
+ shortEn: "Lock",
20539
+ shortIt: "Serratura",
20540
+ aliases: []
20541
+ },
20542
+ {
20543
+ id: "cover",
20544
+ key: "uiLibrary.deviceMeta.noun.type.cover",
20545
+ en: "Cover",
20546
+ it: "Copertura",
20547
+ shortEn: "Cover",
20548
+ shortIt: "Copertura",
20549
+ aliases: []
20550
+ },
20551
+ {
20552
+ id: "valve",
20553
+ key: "uiLibrary.deviceMeta.noun.type.valve",
20554
+ en: "Valve",
20555
+ it: "Valvola",
20556
+ shortEn: "Valve",
20557
+ shortIt: "Valvola",
20558
+ aliases: []
20559
+ },
20560
+ {
20561
+ id: "humidifier",
20562
+ key: "uiLibrary.deviceMeta.noun.type.humidifier",
20563
+ en: "Humidifier",
20564
+ it: "Umidificatore",
20565
+ shortEn: "Humidifier",
20566
+ shortIt: "Umidificatore",
20567
+ aliases: []
20568
+ },
20569
+ {
20570
+ id: "water-heater",
20571
+ key: "uiLibrary.deviceMeta.noun.type.waterHeater",
20572
+ en: "Water heater",
20573
+ it: "Scaldabagno",
20574
+ shortEn: "Heater",
20575
+ shortIt: "Scaldabagno",
20576
+ aliases: []
20577
+ },
20578
+ {
20579
+ id: "fan",
20580
+ key: "uiLibrary.deviceMeta.noun.type.fan",
20581
+ en: "Fan",
20582
+ it: "Ventilatore",
20583
+ shortEn: "Fan",
20584
+ shortIt: "Ventilatore",
20585
+ aliases: []
20586
+ },
20587
+ {
20588
+ id: "media-player",
20589
+ key: "uiLibrary.deviceMeta.noun.type.mediaPlayer",
20590
+ en: "Media player",
20591
+ it: "Lettore multimediale",
20592
+ shortEn: "Player",
20593
+ shortIt: "Lettore",
20594
+ aliases: []
20595
+ },
20596
+ {
20597
+ id: "alarm-panel",
20598
+ key: "uiLibrary.deviceMeta.noun.type.alarmPanel",
20599
+ en: "Alarm panel",
20600
+ it: "Pannello allarme",
20601
+ shortEn: "Alarm",
20602
+ shortIt: "Allarme",
20603
+ aliases: []
20604
+ },
20605
+ {
20606
+ id: "control",
20607
+ key: "uiLibrary.deviceMeta.noun.type.control",
20608
+ en: "Control",
20609
+ it: "Controllo",
20610
+ shortEn: "Control",
20611
+ shortIt: "Controllo",
20612
+ aliases: []
20613
+ },
20614
+ {
20615
+ id: "presence",
20616
+ key: "uiLibrary.deviceMeta.noun.type.presence",
20617
+ en: "Presence",
20618
+ it: "Presenza",
20619
+ shortEn: "Presence",
20620
+ shortIt: "Presenza",
20621
+ aliases: []
20622
+ },
20623
+ {
20624
+ id: "weather",
20625
+ key: "uiLibrary.deviceMeta.noun.type.weather",
20626
+ en: "Weather",
20627
+ it: "Meteo",
20628
+ shortEn: "Weather",
20629
+ shortIt: "Meteo",
20630
+ aliases: []
20631
+ },
20632
+ {
20633
+ id: "vacuum",
20634
+ key: "uiLibrary.deviceMeta.noun.type.vacuum",
20635
+ en: "Vacuum",
20636
+ it: "Aspirapolvere",
20637
+ shortEn: "Vacuum",
20638
+ shortIt: "Aspirapolvere",
20639
+ aliases: []
20640
+ },
20641
+ {
20642
+ id: "lawn-mower",
20643
+ key: "uiLibrary.deviceMeta.noun.type.lawnMower",
20644
+ en: "Lawn mower",
20645
+ it: "Tagliaerba",
20646
+ shortEn: "Mower",
20647
+ shortIt: "Tagliaerba",
20648
+ aliases: []
20649
+ },
20650
+ {
20651
+ id: "container",
20652
+ key: "uiLibrary.deviceMeta.noun.type.container",
20653
+ en: "Container",
20654
+ it: "Contenitore",
20655
+ shortEn: "Container",
20656
+ shortIt: "Contenitore",
20657
+ aliases: []
20658
+ },
20659
+ {
20660
+ id: "image",
20661
+ key: "uiLibrary.deviceMeta.noun.type.image",
20662
+ en: "Image",
20663
+ it: "Immagine",
20664
+ shortEn: "Image",
20665
+ shortIt: "Immagine",
20666
+ aliases: []
20667
+ },
20668
+ {
20669
+ id: "pet-feeder",
20670
+ key: "uiLibrary.deviceMeta.noun.type.petFeeder",
20671
+ en: "Pet feeder",
20672
+ it: "Distributore di cibo per animali",
20673
+ shortEn: "Feeder",
20674
+ shortIt: "Distributore",
20675
+ aliases: []
20676
+ },
20677
+ {
20678
+ id: "dishwasher",
20679
+ key: "uiLibrary.deviceMeta.noun.type.dishwasher",
20680
+ en: "Dishwasher",
20681
+ it: "Lavastoviglie",
20682
+ shortEn: "Dishwasher",
20683
+ shortIt: "Lavastoviglie",
20684
+ aliases: []
20685
+ }
20686
+ ];
20687
+ var FALLBACK_NOUN = {
20688
+ id: "",
20689
+ key: "uiLibrary.deviceMeta.noun.fallback",
20690
+ en: "Device",
20691
+ it: "Dispositivo",
20692
+ shortEn: "Device",
20693
+ shortIt: "Dispositivo",
20694
+ aliases: []
20695
+ };
20696
+ function findNoun(list, id) {
20697
+ if (id === null || id.length === 0) return void 0;
20698
+ return list.find((spec) => spec.id === id);
20699
+ }
20700
+ /** Role wins when it has a noun; otherwise the type. Unknown types fall back. */
20701
+ function nounSpecFor(role, type) {
20702
+ if (role !== null) {
20703
+ const byRole = findNoun(DEVICE_ROLE_NOUNS, role);
20704
+ if (byRole !== void 0) return byRole;
20705
+ }
20706
+ return findNoun(DEVICE_TYPE_NOUNS, type) ?? FALLBACK_NOUN;
20707
+ }
20708
+ function stockPhrases(role, type) {
20709
+ const phrases = [];
20710
+ const specs = [findNoun(DEVICE_ROLE_NOUNS, role), findNoun(DEVICE_TYPE_NOUNS, type)];
20711
+ for (const spec of specs) {
20712
+ if (spec === void 0) continue;
20713
+ phrases.push(spec.en, spec.it, spec.shortEn, spec.shortIt, ...spec.aliases);
20714
+ }
20715
+ return phrases;
20716
+ }
20717
+ function isDeviceNameStock(text, role, type) {
20718
+ const folded = foldDeviceName(text);
20719
+ if (folded.length === 0) return true;
20720
+ return stockPhrases(role, type).some((phrase) => foldDeviceName(phrase) === folded);
20721
+ }
20722
+ /** Noun table for a locale. Tests and the viewer parity spec use this directly. */
20723
+ function deviceNameNouns(locale) {
20724
+ return {
20725
+ shortNoun(role, type) {
20726
+ const spec = nounSpecFor(role, type);
20727
+ return locale === "it" ? spec.shortIt : spec.shortEn;
20728
+ },
20729
+ isStockPhrase(text, role, type) {
20730
+ return isDeviceNameStock(text, role, type);
20731
+ }
20732
+ };
20733
+ }
20734
+ //#endregion
20735
+ //#region src/composites/device-name/use-device-label.ts
20736
+ /**
20737
+ * `useDeviceLabel` is the string `DeviceName` renders — sort keys, aria
20738
+ * labels, confirm copy and search titles call it instead of painting
20739
+ * `device.name`.
20740
+ */
20741
+ function useDeviceNameNouns() {
20742
+ const t = useUiText();
20743
+ return {
20744
+ shortNoun(role, type) {
20745
+ const spec = nounSpecFor(role, type);
20746
+ return t(spec.key, spec.shortEn);
20747
+ },
20748
+ isStockPhrase(text, role, type) {
20749
+ return isDeviceNameStock(text, role, type);
20750
+ }
20751
+ };
20752
+ }
20753
+ function placeholderParent(id) {
20754
+ return {
20755
+ id,
20756
+ name: `#${String(id)}`,
20757
+ type: "unknown",
20758
+ stableId: "",
20759
+ role: null,
20760
+ parentDeviceId: null
20761
+ };
20762
+ }
20763
+ function parentFromIndex(device, index) {
20764
+ const parentId = device.parentDeviceId;
20765
+ if (parentId === null || parentId === void 0) return null;
20766
+ return index.byId.get(parentId) ?? placeholderParent(parentId);
20767
+ }
20768
+ function siblingsFromIndex(device, index) {
20769
+ const parentId = device.parentDeviceId;
20770
+ if (parentId === null || parentId === void 0) return [];
20771
+ const ids = index.childIds.get(parentId) ?? [];
20772
+ const siblings = [];
20773
+ for (const id of ids) {
20774
+ const row = index.byId.get(id);
20775
+ if (row !== void 0) siblings.push(row);
20776
+ }
20777
+ if (!siblings.some((sibling) => sibling.id === device.id)) siblings.push(device);
20778
+ return siblings;
20779
+ }
20780
+ /**
20781
+ * Label for a device the caller already holds. While the fleet has not
20782
+ * answered and no parent was passed, this is the stored name — not `#id`.
20783
+ */
20784
+ function resolveDeviceLabel(device, form, index, nouns, scope) {
20785
+ const subject = index?.byId.get(device.id) ?? device;
20786
+ const parentId = subject.parentDeviceId;
20787
+ if (parentId !== null && parentId !== void 0 && index === null && scope?.parent === void 0) return subject.name;
20788
+ return formatDeviceName(subject, form, {
20789
+ parent: scope?.parent !== void 0 ? scope.parent : index === null ? null : parentFromIndex(subject, index),
20790
+ siblings: scope?.siblings ?? (index === null ? [] : siblingsFromIndex(subject, index))
20791
+ }, nouns);
20792
+ }
20793
+ function useDeviceLabel(device, form, scope) {
20794
+ return resolveDeviceLabel(device, form, useDeviceNameIndex(), useDeviceNameNouns(), scope);
20795
+ }
20796
+ /** `#id` until the fleet answers, and `#id` when the id is not in it. */
20797
+ function useDeviceLabelById(id, form, pendingName) {
20798
+ const index = useDeviceNameIndex();
20799
+ const nouns = useDeviceNameNouns();
20800
+ if (index === null) return pendingName ?? `#${String(id)}`;
20801
+ const device = index.byId.get(id);
20802
+ if (device === void 0) return `#${String(id)}`;
20803
+ return resolveDeviceLabel(device, form, index, nouns);
20804
+ }
20805
+ //#endregion
20806
+ //#region src/composites/device-name/DeviceName.tsx
20807
+ function DeviceName({ device, form, parent, siblings, className, testId }) {
20808
+ const label = useDeviceLabel(device, form, {
20809
+ ...parent !== void 0 ? { parent } : {},
20810
+ ...siblings !== void 0 ? { siblings } : {}
20811
+ });
20812
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
20813
+ className,
20814
+ title: label,
20815
+ "data-testid": testId,
20816
+ children: label
20817
+ });
20818
+ }
20819
+ //#endregion
19627
20820
  //#region src/hooks/use-device-proxy.ts
19628
20821
  /**
19629
20822
  * useDeviceProxy — canonical React entry point for a device.
@@ -20526,6 +21719,9 @@ function GridCellBox({ cell, selected, onSelect, onDragStart }) {
20526
21719
  }, handle))
20527
21720
  });
20528
21721
  }
21722
+ function CameraOptionLabel({ camera }) {
21723
+ return useDeviceLabel(camera, "compact");
21724
+ }
20529
21725
  function CellInspector({ cell, index, cameras, onChange, onRemove }) {
20530
21726
  const t = useUiText();
20531
21727
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -20556,7 +21752,7 @@ function CellInspector({ cell, index, cameras, onChange, onRemove }) {
20556
21752
  className: "flex-1 rounded border border-border bg-transparent px-1 py-0.5",
20557
21753
  children: cameras.map((camera) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
20558
21754
  value: String(camera.id),
20559
- children: camera.name
21755
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CameraOptionLabel, { camera })
20560
21756
  }, camera.id))
20561
21757
  })]
20562
21758
  }),
@@ -31013,13 +32209,13 @@ function InfoBox({ field }) {
31013
32209
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
31014
32210
  className: `rounded-md border-l-4 px-3 py-2.5 ${styles.border} ${styles.bg}`,
31015
32211
  children: [field.label && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
31016
- className: `text-xs font-semibold mb-0.5 ${styles.text}`,
32212
+ className: field.content.length > 0 ? `text-xs font-semibold mb-0.5 ${styles.text}` : "text-xs text-foreground-subtle leading-relaxed whitespace-pre-line break-words",
31017
32213
  children: field.label
31018
32214
  }), isHtml ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
31019
32215
  className: "text-xs text-foreground-subtle leading-relaxed info-html-content",
31020
32216
  dangerouslySetInnerHTML: { __html: sanitizeInfoHtml(field.content) }
31021
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
31022
- className: "text-xs text-foreground-subtle leading-relaxed whitespace-pre-line",
32217
+ }) : field.content.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
32218
+ className: "text-xs text-foreground-subtle leading-relaxed whitespace-pre-line break-words",
31023
32219
  children: field.content
31024
32220
  })]
31025
32221
  })
@@ -48244,17 +49440,19 @@ function SensorHeroInner({ trpc, device }) {
48244
49440
  if (resolution.kind === "quiet") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
48245
49441
  className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-border bg-surface p-4 min-h-[96px]",
48246
49442
  "data-testid": "sensor-hero-quiet",
48247
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48248
- className: "text-sm text-foreground-subtle",
48249
- children: device.name
49443
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49444
+ device,
49445
+ form: "full",
49446
+ className: "text-sm text-foreground-subtle"
48250
49447
  })
48251
49448
  });
48252
49449
  const slice = resolution.kind === "value" ? resolution.slice : void 0;
48253
49450
  if (descriptor === void 0 || slice === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
48254
49451
  className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-border bg-surface p-4 min-h-[96px]",
48255
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48256
- className: "text-sm text-foreground-subtle",
48257
- children: device.name
49452
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49453
+ device,
49454
+ form: "full",
49455
+ className: "text-sm text-foreground-subtle"
48258
49456
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48259
49457
  className: "text-xs text-foreground-subtle italic",
48260
49458
  children: t("uiLibrary.sensorHeroCard.awaiting", "Awaiting state…")
@@ -48294,9 +49492,10 @@ function SensorHeroInner({ trpc, device }) {
48294
49492
  "data-testid": "sensor-hero-occupancy",
48295
49493
  children: occupancyOn ? t("uiLibrary.sensorHeroCard.occupied", "Occupied") : t("uiLibrary.sensorHeroCard.clear", "Clear")
48296
49494
  }),
48297
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48298
- className: "text-sm text-foreground-subtle",
48299
- children: device.name
49495
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49496
+ device,
49497
+ form: "full",
49498
+ className: "text-sm text-foreground-subtle"
48300
49499
  })
48301
49500
  ]
48302
49501
  });
@@ -48311,9 +49510,10 @@ function SensorHeroInner({ trpc, device }) {
48311
49510
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48312
49511
  className: cn("inline-flex items-center rounded px-3 py-1.5 text-lg font-semibold", active ? "bg-warning/15 text-warning" : "bg-surface-subtle text-foreground-subtle"),
48313
49512
  children: label
48314
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48315
- className: "text-sm text-foreground-subtle",
48316
- children: device.name
49513
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49514
+ device,
49515
+ form: "full",
49516
+ className: "text-sm text-foreground-subtle"
48317
49517
  })]
48318
49518
  });
48319
49519
  }
@@ -48321,9 +49521,10 @@ function SensorHeroInner({ trpc, device }) {
48321
49521
  if (!isStringSliceValue(slice)) return null;
48322
49522
  if (slice.value === "") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
48323
49523
  className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-border bg-surface p-4 min-h-[96px]",
48324
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48325
- className: "text-sm text-foreground-subtle",
48326
- children: device.name
49524
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49525
+ device,
49526
+ form: "full",
49527
+ className: "text-sm text-foreground-subtle"
48327
49528
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48328
49529
  className: "text-xs text-foreground-subtle italic",
48329
49530
  children: t("uiLibrary.sensorHeroCard.awaiting", "Awaiting state…")
@@ -48335,9 +49536,10 @@ function SensorHeroInner({ trpc, device }) {
48335
49536
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48336
49537
  className: "text-3xl font-semibold tabular-nums text-foreground",
48337
49538
  children: formatControlDateTime(slice.value, format)
48338
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48339
- className: "text-sm text-foreground-subtle",
48340
- children: device.name
49539
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49540
+ device,
49541
+ form: "full",
49542
+ className: "text-sm text-foreground-subtle"
48341
49543
  })]
48342
49544
  });
48343
49545
  }
@@ -48349,9 +49551,10 @@ function SensorHeroInner({ trpc, device }) {
48349
49551
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48350
49552
  className: "text-xl font-semibold text-foreground",
48351
49553
  children: slice.value
48352
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48353
- className: "text-sm text-foreground-subtle",
48354
- children: device.name
49554
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49555
+ device,
49556
+ form: "full",
49557
+ className: "text-sm text-foreground-subtle"
48355
49558
  })]
48356
49559
  });
48357
49560
  }
@@ -48362,9 +49565,10 @@ function SensorHeroInner({ trpc, device }) {
48362
49565
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48363
49566
  className: cn("inline-flex items-center rounded px-3 py-1.5 text-lg font-semibold", slice.state === "home" ? "bg-success/15 text-success" : "bg-surface-subtle text-foreground-subtle"),
48364
49567
  children: label
48365
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48366
- className: "text-sm text-foreground-subtle",
48367
- children: device.name
49568
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49569
+ device,
49570
+ form: "full",
49571
+ className: "text-sm text-foreground-subtle"
48368
49572
  })]
48369
49573
  });
48370
49574
  }
@@ -55119,7 +56323,9 @@ var CONTROL_FILLS = {
55119
56323
  [_camstack_types.DeviceType.Container]: false,
55120
56324
  [_camstack_types.DeviceType.Image]: false,
55121
56325
  [_camstack_types.DeviceType.PetFeeder]: false,
55122
- [_camstack_types.DeviceType.Dishwasher]: false
56326
+ [_camstack_types.DeviceType.Dishwasher]: false,
56327
+ [_camstack_types.DeviceType.Washer]: false,
56328
+ [_camstack_types.DeviceType.Dryer]: false
55123
56329
  };
55124
56330
  var DEVICE_TYPE_VALUES = new Set(Object.values(_camstack_types.DeviceType));
55125
56331
  /**
@@ -56375,6 +57581,7 @@ function useContainerChildren(deviceId) {
56375
57581
  var SENSOR_VALUE_TYPES = new Set([_camstack_types.DeviceType.Sensor, _camstack_types.DeviceType.Presence]);
56376
57582
  function DeviceItemActions({ trpc, device, controlPlacement }) {
56377
57583
  const containerChildren = useContainerChildren(device.id);
57584
+ const deviceLabel = useDeviceLabel(device, "compact");
56378
57585
  const placement = controlPlacement ?? "right";
56379
57586
  if (!!device.disabled) return null;
56380
57587
  if (device.type === _camstack_types.DeviceType.Container) {
@@ -56430,7 +57637,7 @@ function DeviceItemActions({ trpc, device, controlPlacement }) {
56430
57637
  showReboot && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RebootQuickAction, {
56431
57638
  trpc,
56432
57639
  deviceId: device.id,
56433
- deviceName: device.name
57640
+ deviceName: deviceLabel
56434
57641
  }),
56435
57642
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdminLinkQuickAction, {
56436
57643
  trpc,
@@ -56666,9 +57873,16 @@ function ChildRow$1({ trpc, child, onNavigate, parentName }) {
56666
57873
  running: runningSlice === void 0 || runningSlice.running === null ? null : runningSlice.running,
56667
57874
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChildIcon, { className: "h-3 w-3 text-foreground-subtle flex-shrink-0" })
56668
57875
  }),
56669
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
56670
- className: "text-[10.5px] text-foreground flex-1 truncate",
56671
- children: childListName(child, parentName)
57876
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
57877
+ device: child,
57878
+ form: "nested",
57879
+ parent: {
57880
+ id: child.parentDeviceId ?? 0,
57881
+ name: parentName,
57882
+ type: "unknown",
57883
+ stableId: ""
57884
+ },
57885
+ className: "text-[10.5px] text-foreground flex-1 truncate"
56672
57886
  }),
56673
57887
  showMotionTrigger && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChildMotionTriggerToggle, {
56674
57888
  trpc,
@@ -56775,8 +57989,9 @@ function IconAction({ icon: Icon, label, onClick, danger = false }) {
56775
57989
  function DeviceDeleteAction({ device, onDelete }) {
56776
57990
  const t = useUiText();
56777
57991
  const confirm = useConfirm();
57992
+ const deviceLabel = useDeviceLabel(device, "full");
56778
57993
  const requestDelete = async () => {
56779
- const label = device.name.trim() !== "" ? device.name : `#${String(device.id)}`;
57994
+ const label = deviceLabel.trim() !== "" ? deviceLabel : `#${String(device.id)}`;
56780
57995
  if (await confirm({
56781
57996
  title: t("uiLibrary.deviceDeleteAction.title", "Delete device?"),
56782
57997
  message: t("uiLibrary.deviceDeleteAction.message", "This will remove \"{{label}}\" and all its settings. This action cannot be undone.", { label }),
@@ -57059,7 +58274,7 @@ var CHEVRON_SLOT_CLASS = {
57059
58274
  horizontal: "h-3.5 w-3.5",
57060
58275
  stacked: "h-4 w-4 mt-0.5"
57061
58276
  };
57062
- function DeviceItemHeader({ trpc, device, variant, flags, parent, integrationIcon, hasChildren, expanded, onToggleExpand, onNavigateToParent, stacked = false, nameOverflow = "truncate" }) {
58277
+ function DeviceItemHeader({ trpc, device, variant, flags, parent, integrationIcon, hasChildren, expanded, onToggleExpand, onNavigateToParent, stacked = false, nameOverflow = "truncate", nameForm = "compact" }) {
57063
58278
  const t = useUiText();
57064
58279
  const containerChildren = useContainerChildren(device.id);
57065
58280
  const roleDefaults = useRoleDisplayDefaults();
@@ -57070,6 +58285,13 @@ function DeviceItemHeader({ trpc, device, variant, flags, parent, integrationIco
57070
58285
  const slotClass = stacked ? CHEVRON_SLOT_CLASS.stacked : CHEVRON_SLOT_CLASS.horizontal;
57071
58286
  const iconClass = cn("text-foreground-subtle flex-shrink-0", stacked ? "h-4 w-4 mt-0.5" : variant === "minimal" ? "h-3 w-3" : "h-3.5 w-3.5");
57072
58287
  const lineClass = nameOverflow === "scroll" ? NAME_TEXT_CLASS : "truncate";
58288
+ const nameParent = nameForm === "nested" && parent !== null ? {
58289
+ id: parent.id,
58290
+ name: parent.name,
58291
+ type: parent.type ?? "unknown",
58292
+ stableId: ""
58293
+ } : void 0;
58294
+ const parentLabel = useDeviceLabelById(parent?.id ?? 0, "compact", parent?.name);
57073
58295
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
57074
58296
  className: cn(stacked ? "flex items-start gap-2 w-full min-w-0" : "flex items-center gap-2 flex-1 min-w-0"),
57075
58297
  children: [
@@ -57100,11 +58322,12 @@ function DeviceItemHeader({ trpc, device, variant, flags, parent, integrationIco
57100
58322
  children: [flags.showIntegrationIcon && integrationIcon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
57101
58323
  className: "flex-shrink-0",
57102
58324
  children: integrationIcon
57103
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
57104
- "data-testid": "device-name",
57105
- className: cn("font-semibold text-foreground", stacked ? "text-[13px] leading-snug line-clamp-2 break-words" : cn(lineClass, variant === "minimal" ? "text-[10.5px]" : "text-[11px]")),
57106
- title: device.name,
57107
- children: device.name
58325
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
58326
+ device,
58327
+ form: nameForm,
58328
+ ...nameParent !== void 0 ? { parent: nameParent } : {},
58329
+ testId: "device-name",
58330
+ className: cn("font-semibold text-foreground", stacked ? "text-[13px] leading-snug line-clamp-2 break-words" : cn(lineClass, variant === "minimal" ? "text-[10.5px]" : "text-[11px]"))
57108
58331
  })]
57109
58332
  }),
57110
58333
  (flags.showId || flags.showStableId) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -57133,12 +58356,12 @@ function DeviceItemHeader({ trpc, device, variant, flags, parent, integrationIco
57133
58356
  },
57134
58357
  className: cn("mt-0.5 inline-flex items-center gap-1 text-[9px] text-foreground-subtle hover:text-primary transition-colors", nameOverflow === "scroll" ? "md:max-w-full" : "max-w-full"),
57135
58358
  title: t("uiLibrary.deviceItemHeader.openParent", "Open parent: {{name}} (#{{id}})", {
57136
- name: parent.name,
58359
+ name: parentLabel,
57137
58360
  id: parent.id
57138
58361
  }),
57139
58362
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Network, { className: "h-2.5 w-2.5 shrink-0" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
57140
58363
  className: lineClass,
57141
- children: t("uiLibrary.deviceItemHeader.under", "Under: {{name}}", { name: parent.name })
58364
+ children: t("uiLibrary.deviceItemHeader.under", "Under: {{name}}", { name: parentLabel })
57142
58365
  })]
57143
58366
  })
57144
58367
  ]
@@ -57267,7 +58490,9 @@ function HoverZoomImage({ src, alt, caption, placeholder = "No image", thumbClas
57267
58490
  * the 288 px hover zoom. */
57268
58491
  var PREVIEW_SNAPSHOT_WIDTH = 480;
57269
58492
  function DeviceItemPreview({ trpc, device, status, enabled, showStatusPills = false }) {
57270
- const snapshot = useDeviceSnapshotImage(enabled && device.online ? device.id : null, { width: PREVIEW_SNAPSHOT_WIDTH });
58493
+ const liveActive = enabled && device.online;
58494
+ const deviceLabel = useDeviceLabel(device, "compact");
58495
+ const snapshot = useDeviceSnapshotImage(liveActive ? device.id : null, { width: PREVIEW_SNAPSHOT_WIDTH });
57271
58496
  const isBatteryOperated = (device.features ?? []).includes("battery-operated");
57272
58497
  const wantPills = showStatusPills && isBatteryOperated && !device.disabled;
57273
58498
  const batteryStatus = useDeviceBattery(trpc, wantPills ? device.id : null);
@@ -57283,7 +58508,7 @@ function DeviceItemPreview({ trpc, device, status, enabled, showStatusPills = fa
57283
58508
  wantLinkPill && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NetworkLinkBadge, { status: linkStatus }),
57284
58509
  enabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HoverZoomImage, {
57285
58510
  src: offline ? null : snapshot.src,
57286
- alt: device.name,
58511
+ alt: deviceLabel,
57287
58512
  placeholder,
57288
58513
  thumbClassName: "h-7 w-7 rounded"
57289
58514
  })
@@ -57506,6 +58731,14 @@ function DeviceItem(props) {
57506
58731
  const flags = resolveFlags(props);
57507
58732
  const variant = props.variant ?? "compact";
57508
58733
  const view = props.view ?? "card";
58734
+ const nameForm = props.isAccessoryRow ? "nested" : "compact";
58735
+ const nameParent = nameForm === "nested" && parent ? {
58736
+ id: parent.id,
58737
+ name: parent.name,
58738
+ type: parent.type ?? "unknown",
58739
+ stableId: ""
58740
+ } : void 0;
58741
+ const rowLabel = useDeviceLabel(device, nameForm, nameParent !== void 0 ? { parent: nameParent } : void 0);
57509
58742
  const enabled = !device.disabled;
57510
58743
  const isCamera = device.isCamera ?? device.type.toLowerCase() === "camera";
57511
58744
  const childrenList = props.children ?? [];
@@ -57581,7 +58814,7 @@ function DeviceItem(props) {
57581
58814
  checked: selection.selected,
57582
58815
  onChange: selection.onToggle,
57583
58816
  onClick: (e) => e.stopPropagation(),
57584
- "aria-label": t("uiLibrary.deviceItem.select", "Select {{name}}", { name: device.name }),
58817
+ "aria-label": t("uiLibrary.deviceItem.select", "Select {{name}}", { name: rowLabel }),
57585
58818
  className: "flex-shrink-0"
57586
58819
  }),
57587
58820
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceItemHeader, {
@@ -57590,6 +58823,7 @@ function DeviceItem(props) {
57590
58823
  variant,
57591
58824
  flags,
57592
58825
  parent: parent ?? null,
58826
+ nameForm,
57593
58827
  integrationIcon: integrationIcon ?? null,
57594
58828
  hasChildren,
57595
58829
  expanded,
@@ -57658,7 +58892,7 @@ function DeviceItem(props) {
57658
58892
  checked: selection.selected,
57659
58893
  onChange: selection.onToggle,
57660
58894
  onClick: (e) => e.stopPropagation(),
57661
- "aria-label": t("uiLibrary.deviceItem.select", "Select {{name}}", { name: device.name }),
58895
+ "aria-label": t("uiLibrary.deviceItem.select", "Select {{name}}", { name: rowLabel }),
57662
58896
  className: "flex-shrink-0 mt-0.5"
57663
58897
  }),
57664
58898
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceItemHeader, {
@@ -57667,6 +58901,7 @@ function DeviceItem(props) {
57667
58901
  variant,
57668
58902
  flags,
57669
58903
  parent: parent ?? null,
58904
+ nameForm,
57670
58905
  integrationIcon: integrationIcon ?? null,
57671
58906
  hasChildren,
57672
58907
  expanded,
@@ -57727,6 +58962,14 @@ function DeviceItemTableRow(props) {
57727
58962
  const t = useUiText();
57728
58963
  const { trpc, device, variant, flags, parent, integrationIcon, accessoryCount, adoptedCount, hasChildren, expanded, onToggleExpand, onNavigate, onRowClick, rowSelects, rowSelected, displayStatus, isCamera, showSnapshot, isAccessoryRow, indentLevel, className, renderRowAction, onDelete, selection, showTypeCell, showManufacturerCell, showFeaturesCell } = props;
57729
58964
  const containerChildren = useContainerChildren(device.id);
58965
+ const nameForm = isAccessoryRow ? "nested" : "compact";
58966
+ const nameParent = nameForm === "nested" && parent ? {
58967
+ id: parent.id,
58968
+ name: parent.name,
58969
+ type: parent.type ?? "unknown",
58970
+ stableId: ""
58971
+ } : void 0;
58972
+ const rowLabel = useDeviceLabel(device, nameForm, nameParent !== void 0 ? { parent: nameParent } : void 0);
57730
58973
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
57731
58974
  onClick: onRowClick,
57732
58975
  ...rowSelects ? {
@@ -57751,7 +58994,7 @@ function DeviceItemTableRow(props) {
57751
58994
  checked: selection.selected,
57752
58995
  onChange: selection.onToggle,
57753
58996
  onClick: (e) => e.stopPropagation(),
57754
- "aria-label": t("uiLibrary.deviceItem.select", "Select {{name}}", { name: device.name }),
58997
+ "aria-label": t("uiLibrary.deviceItem.select", "Select {{name}}", { name: rowLabel }),
57755
58998
  className: "flex-shrink-0"
57756
58999
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceItemHeader, {
57757
59000
  trpc,
@@ -57759,6 +59002,7 @@ function DeviceItemTableRow(props) {
57759
59002
  variant,
57760
59003
  flags,
57761
59004
  parent,
59005
+ nameForm,
57762
59006
  integrationIcon,
57763
59007
  hasChildren,
57764
59008
  expanded,
@@ -58497,13 +59741,13 @@ function TableRowGroup({ parent, accessories, hasAccessories, expanded, onToggle
58497
59741
  function AccessorySubRow({ trpc, accessory, parentName, showType, showManufacturer, showFeatures, onNavigate, muted }) {
58498
59742
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceItem, {
58499
59743
  trpc,
58500
- device: {
58501
- ...accessory,
58502
- name: childListName(accessory, parentName)
58503
- },
59744
+ device: accessory,
58504
59745
  view: "table",
58505
59746
  integrationIcon: null,
58506
- parent: null,
59747
+ parent: accessory.parentDeviceId === null ? null : {
59748
+ id: accessory.parentDeviceId,
59749
+ name: parentName
59750
+ },
58507
59751
  isAccessoryRow: true,
58508
59752
  showChildrenAccordion: false,
58509
59753
  onNavigate: onNavigate ?? void 0,
@@ -59302,6 +60546,15 @@ function DeviceExportSetup({ addonId }) {
59302
60546
  }), status.setup && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SetupSection, { setup: status.setup })]
59303
60547
  });
59304
60548
  }
60549
+ function ExportSelectCell({ device, checked, onToggle }) {
60550
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
60551
+ type: "checkbox",
60552
+ checked,
60553
+ onChange: onToggle,
60554
+ onClick: (e) => e.stopPropagation(),
60555
+ "aria-label": useUiText()("uiLibrary.deviceExport.select", "Select {{name}}", { name: useDeviceLabel(device, "compact") })
60556
+ });
60557
+ }
59305
60558
  /**
59306
60559
  * Per-row Expose/Exposed toggle. Reflects whether the row's device is exposed
59307
60560
  * to THIS exporter and flips it through the SAME runner the bulk buttons use —
@@ -59313,6 +60566,7 @@ function DeviceExportSetup({ addonId }) {
59313
60566
  */
59314
60567
  function ExportRowToggle({ addonId, device, exposed, busy, onToggle }) {
59315
60568
  const t = useUiText();
60569
+ const deviceLabel = useDeviceLabel(device, "compact");
59316
60570
  const handleClick = (e) => {
59317
60571
  e.stopPropagation();
59318
60572
  if (busy) return;
@@ -59323,7 +60577,7 @@ function ExportRowToggle({ addonId, device, exposed, busy, onToggle }) {
59323
60577
  variant: exposed ? "ghost" : "primary",
59324
60578
  disabled: busy,
59325
60579
  onClick: handleClick,
59326
- "aria-label": exposed ? t("uiLibrary.deviceExport.unexposeDevice", "Unexpose {{name}}", { name: device.name }) : t("uiLibrary.deviceExport.exposeDevice", "Expose {{name}}", { name: device.name }),
60580
+ "aria-label": exposed ? t("uiLibrary.deviceExport.unexposeDevice", "Unexpose {{name}}", { name: deviceLabel }) : t("uiLibrary.deviceExport.exposeDevice", "Expose {{name}}", { name: deviceLabel }),
59327
60581
  title: exposed ? t("uiLibrary.deviceExport.stopTitle", "Stop exposing to \"{{addon}}\"", { addon: addonId }) : t("uiLibrary.deviceExport.exposeTitle", "Expose to \"{{addon}}\"", { addon: addonId }),
59328
60582
  children: exposed ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Unlink2, { className: "mr-1 h-3.5 w-3.5" }), t("uiLibrary.deviceExport.exposed", "Exposed")] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Share2, { className: "mr-1 h-3.5 w-3.5" }), t("uiLibrary.deviceExport.expose", "Expose")] })
59329
60583
  });
@@ -59539,12 +60793,10 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
59539
60793
  key: "select",
59540
60794
  header: "",
59541
60795
  headerClassName: "w-8",
59542
- cell: (device) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
59543
- type: "checkbox",
60796
+ cell: (device) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExportSelectCell, {
60797
+ device,
59544
60798
  checked: selectedIds.has(String(device.id)),
59545
- onChange: () => toggleSelection(String(device.id)),
59546
- onClick: (e) => e.stopPropagation(),
59547
- "aria-label": t("uiLibrary.deviceExport.select", "Select {{name}}", { name: device.name })
60799
+ onToggle: () => toggleSelection(String(device.id))
59548
60800
  })
59549
60801
  },
59550
60802
  {
@@ -59563,9 +60815,10 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
59563
60815
  {
59564
60816
  key: "name",
59565
60817
  header: t("uiLibrary.deviceExport.colName", "Name"),
59566
- cell: (device) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
59567
- className: "text-xs font-medium text-foreground",
59568
- children: device.name
60818
+ cell: (device) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
60819
+ device,
60820
+ form: "compact",
60821
+ className: "text-xs font-medium text-foreground"
59569
60822
  })
59570
60823
  },
59571
60824
  {
@@ -61148,28 +62401,61 @@ function FormField$1({ label, description, error, required, children, orientatio
61148
62401
  }
61149
62402
  //#endregion
61150
62403
  //#region src/composites/grouped-tile-picker.tsx
62404
+ /**
62405
+ * `GroupedTilePicker` — choose ONE "type of thing" from tiles grouped by who
62406
+ * provides it (an AI profile kind, a notification-target kind…): an uppercase
62407
+ * group label, then a grid of icon + label tiles. The pre-modal of a
62408
+ * create-flow whose form depends on the choice.
62409
+ *
62410
+ * The tile is deliberately not a `Button`: a vertical icon-over-label card in
62411
+ * a grid, which toolbar-button chrome fights. One place owns that look so the
62412
+ * pickers stay identical.
62413
+ */
61151
62414
  function GroupedTilePicker({ groups, onPick, emptyMessage }) {
62415
+ const [openKey, setOpenKey] = (0, react$1.useState)(null);
62416
+ const openTile = openKey === null ? null : groups.flatMap((group) => group.tiles).find((tile) => tile.key === openKey) ?? null;
61152
62417
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
61153
62418
  className: "space-y-4",
61154
- children: [groups.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
61155
- className: "text-xs text-foreground-subtle",
61156
- children: emptyMessage
61157
- }), groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
61158
- className: "mb-1.5 text-[10px] font-semibold uppercase tracking-wider text-foreground-subtle",
61159
- children: group.label
61160
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
61161
- className: "grid grid-cols-2 gap-2 sm:grid-cols-3",
61162
- children: group.tiles.map((tile) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
61163
- type: "button",
61164
- "data-testid": tile.testId,
61165
- onClick: () => onPick(tile.key),
61166
- className: "flex flex-col items-center gap-1.5 rounded-lg border border-border bg-surface px-3 py-3 text-xs transition-colors hover:border-primary/50 hover:bg-surface-hover",
61167
- children: [tile.icon, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
61168
- className: "text-center font-medium text-foreground",
61169
- children: tile.label
61170
- })]
61171
- }, tile.key))
61172
- })] }, group.id))]
62419
+ children: [
62420
+ groups.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
62421
+ className: "text-xs text-foreground-subtle",
62422
+ children: emptyMessage
62423
+ }),
62424
+ groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
62425
+ className: "mb-1.5 text-[10px] font-semibold uppercase tracking-wider text-foreground-subtle",
62426
+ children: group.label
62427
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
62428
+ className: "grid grid-cols-2 gap-2 sm:grid-cols-3",
62429
+ children: group.tiles.map((tile) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
62430
+ className: "relative",
62431
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
62432
+ type: "button",
62433
+ "data-testid": tile.testId,
62434
+ onClick: () => onPick(tile.key),
62435
+ className: "flex h-full w-full flex-col items-center gap-1.5 rounded-lg border border-border bg-surface px-3 py-3 text-xs transition-colors hover:border-primary/50 hover:bg-surface-hover",
62436
+ children: [tile.icon, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
62437
+ className: "text-center font-medium text-foreground",
62438
+ children: tile.label
62439
+ })]
62440
+ }), tile.detail !== void 0 && tile.detailLabel !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
62441
+ className: "absolute top-1 right-1",
62442
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
62443
+ type: "button",
62444
+ variant: "ghost",
62445
+ size: "sm",
62446
+ icon: Info,
62447
+ "aria-label": tile.detailLabel,
62448
+ onClick: () => setOpenKey((current) => current === tile.key ? null : tile.key)
62449
+ })
62450
+ })]
62451
+ }, tile.key))
62452
+ })] }, group.id)),
62453
+ openTile?.detail !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BottomSheet, {
62454
+ open: true,
62455
+ onClose: () => setOpenKey(null),
62456
+ children: openTile.detail
62457
+ })
62458
+ ]
61173
62459
  });
61174
62460
  }
61175
62461
  //#endregion
@@ -66345,6 +67631,26 @@ function deviceGroupTitle(group, t = defaultUiText) {
66345
67631
  function isDeviceNoun(noun) {
66346
67632
  return noun === "camera" || noun === "device";
66347
67633
  }
67634
+ /** Compact label for a device id. String ids (nodes, packages) keep the stored text. */
67635
+ function usePickerDeviceLabel(id, stored) {
67636
+ const lookedUp = useDeviceLabelById(typeof id === "number" ? id : 0, "compact", stored);
67637
+ return typeof id === "number" ? lookedUp : stored;
67638
+ }
67639
+ function PickerDeviceName({ id, stored, className }) {
67640
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
67641
+ className,
67642
+ children: usePickerDeviceLabel(id, stored)
67643
+ });
67644
+ }
67645
+ function PickerChip({ id, stored, removed, onRemove }) {
67646
+ const label = usePickerDeviceLabel(id, stored);
67647
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectedChip, {
67648
+ testId: `picker-chip-${String(id)}`,
67649
+ label,
67650
+ warning: removed,
67651
+ ...onRemove !== void 0 ? { onRemove } : {}
67652
+ });
67653
+ }
66348
67654
  function chipsOf(value, devices, fleet, noun, t) {
66349
67655
  const byId = new Map(devices.map((device) => [device.id, device.name]));
66350
67656
  const orphans = new Map(offListSelections(value, devices, fleet.devices, fleet.state).map((row) => [row.id, row]));
@@ -66380,7 +67686,7 @@ function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, s
66380
67686
  const t = useUiText();
66381
67687
  const groupTitle = groupTitleProp ?? ((group) => deviceGroupTitle(group, t));
66382
67688
  const lockedNote = lockedNoteProp ?? t("uiLibrary.devicePicker.alreadyAdded", "Already added");
66383
- const devices = (0, react$1.useMemo)(() => withResolvedParentDetail(devicesIn), [devicesIn]);
67689
+ const devices = (0, react$1.useMemo)(() => isDeviceNoun(noun) ? devicesIn : withResolvedParentDetail(devicesIn), [devicesIn, noun]);
66384
67690
  const judge = fleet ?? {
66385
67691
  state: listState,
66386
67692
  devices: listState === "answered" ? devices : void 0
@@ -66415,10 +67721,10 @@ function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, s
66415
67721
  chips.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
66416
67722
  className: "text-[11px] text-foreground-subtle",
66417
67723
  children: emptyLabel ?? (emptyMeansAll ? t("uiLibrary.devicePicker.everyNoRestriction", "Every {{noun}} — no restriction", { noun: pickerNounLabel(noun, t) }) : t("uiLibrary.devicePicker.noneSelected", "None selected"))
66418
- }) : overflow.shown.map((chip) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectedChip, {
66419
- testId: `picker-chip-${String(chip.id)}`,
66420
- label: chip.label,
66421
- warning: chip.removed,
67724
+ }) : overflow.shown.map((chip) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PickerChip, {
67725
+ id: chip.id,
67726
+ stored: chip.label,
67727
+ removed: chip.removed,
66422
67728
  ...!disabled ? { onRemove: () => onChange(value.filter((id) => id !== chip.id)) } : {}
66423
67729
  }, chip.id)),
66424
67730
  overflow.hidden > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -66711,9 +68017,10 @@ function PickerRowView({ row, value, onChange, onToggleGroup, annotate, groupTit
66711
68017
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
66712
68018
  className: "flex min-w-0 flex-1 flex-col",
66713
68019
  children: [
66714
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
66715
- className: "truncate",
66716
- children: row.device.name
68020
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PickerDeviceName, {
68021
+ id: row.device.id,
68022
+ stored: row.device.name,
68023
+ className: "truncate"
66717
68024
  }),
66718
68025
  second !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
66719
68026
  className: "truncate text-[10px] text-foreground-subtle",
@@ -69804,10 +71111,14 @@ exports.DEVICE_COLUMNS = DEVICE_COLUMNS;
69804
71111
  exports.DEVICE_EXPORT_PAGE_SIZE_KEY = DEVICE_EXPORT_PAGE_SIZE_KEY;
69805
71112
  exports.DEVICE_LIST_PAGE_SIZE_KEY = DEVICE_LIST_PAGE_SIZE_KEY;
69806
71113
  exports.DEVICE_LIST_PAGE_SIZE_OPTIONS = DEVICE_LIST_PAGE_SIZE_OPTIONS;
71114
+ exports.DEVICE_NAME_FORMS = DEVICE_NAME_FORMS;
71115
+ exports.DEVICE_NAME_JOIN = DEVICE_NAME_JOIN;
69807
71116
  exports.DEVICE_PICKER_GROUP_STORAGE_KEY = DEVICE_PICKER_GROUP_STORAGE_KEY;
69808
71117
  exports.DEVICE_ROLE_META = DEVICE_ROLE_META;
71118
+ exports.DEVICE_ROLE_NOUNS = DEVICE_ROLE_NOUNS;
69809
71119
  exports.DEVICE_TYPE_CONTROL = DEVICE_TYPE_CONTROL;
69810
71120
  exports.DEVICE_TYPE_META = DEVICE_TYPE_META;
71121
+ exports.DEVICE_TYPE_NOUNS = DEVICE_TYPE_NOUNS;
69811
71122
  exports.DataRecordsHub = DataRecordsHub;
69812
71123
  exports.DataTable = DataTable;
69813
71124
  exports.DataTableFilterBar = DataTableFilterBar;
@@ -69824,11 +71135,14 @@ exports.DeviceContextProvider = DeviceContextProvider;
69824
71135
  exports.DeviceExportPanel = DeviceExportPanel;
69825
71136
  exports.DeviceExportSetup = DeviceExportSetup;
69826
71137
  exports.DeviceFilterBar = DeviceFilterBar;
71138
+ exports.DeviceFleetFromServer = DeviceFleetFromServer;
71139
+ exports.DeviceFleetProvider = DeviceFleetProvider;
69827
71140
  exports.DeviceGrid = DeviceGrid;
69828
71141
  exports.DeviceHistoryView = DeviceHistoryView;
69829
71142
  exports.DeviceItem = DeviceItem;
69830
71143
  exports.DeviceList = DeviceList;
69831
71144
  exports.DeviceMultiSelectField = DeviceMultiSelectField;
71145
+ exports.DeviceName = DeviceName;
69832
71146
  exports.DevicePicker = DevicePicker;
69833
71147
  exports.DeviceQuickDetailProvider = DeviceQuickDetailProvider;
69834
71148
  exports.DeviceSelectField = DeviceSelectField;
@@ -70257,6 +71571,7 @@ exports.ViewSwitch = ViewSwitch;
70257
71571
  exports.VodPlaybackProvider = VodPlaybackProvider;
70258
71572
  exports.Volume2 = Volume2;
70259
71573
  exports.VolumeX = VolumeX;
71574
+ exports.WashingMachine = WashingMachine;
70260
71575
  exports.WaterHeaterHeroCard = WaterHeaterHeroCard;
70261
71576
  exports.WaterHeaterInlineControl = WaterHeaterInlineControl;
70262
71577
  exports.Waves = Waves;
@@ -70359,6 +71674,7 @@ exports.describeDeviceSelector = describeDeviceSelector;
70359
71674
  exports.deviceIntegrationResolver = deviceIntegrationResolver;
70360
71675
  exports.deviceListStateOf = deviceListStateOf;
70361
71676
  exports.deviceMatchesFilter = deviceMatchesFilter;
71677
+ exports.deviceNameNouns = deviceNameNouns;
70362
71678
  exports.deviceOptionLabel = deviceOptionLabel;
70363
71679
  exports.deviceRoleMeta = deviceRoleMeta;
70364
71680
  exports.deviceRoleMetaOf = deviceRoleMetaOf;
@@ -70386,10 +71702,12 @@ exports.findTimezone = _camstack_types.findTimezone;
70386
71702
  exports.flattenPickerGrouping = flattenPickerGrouping;
70387
71703
  exports.flattenPickerRows = flattenPickerRows;
70388
71704
  exports.fleetViewOf = fleetViewOf;
71705
+ exports.foldDeviceName = foldDeviceName;
70389
71706
  exports.formatChartBytes = formatChartBytes;
70390
71707
  exports.formatChartNumber = formatChartNumber;
70391
71708
  exports.formatChartValue = formatChartValue;
70392
71709
  exports.formatControlDateTime = formatControlDateTime;
71710
+ exports.formatDeviceName = formatDeviceName;
70393
71711
  exports.formatLastSeen = formatLastSeen;
70394
71712
  exports.formatNumeric = formatNumeric;
70395
71713
  exports.formatSettingValue = formatSettingValue;
@@ -70417,6 +71735,7 @@ exports.humanizeName = humanizeName;
70417
71735
  exports.humidifierTint = humidifierTint;
70418
71736
  exports.iconGlyphsFailed = iconGlyphsFailed;
70419
71737
  exports.iconGlyphsLoaded = iconGlyphsLoaded;
71738
+ exports.indexDeviceNames = indexDeviceNames;
70420
71739
  exports.initialScrubState = initialScrubState;
70421
71740
  exports.insertAtSelection = insertAtSelection;
70422
71741
  exports.isAbsentProvider = isAbsentProvider;
@@ -70424,6 +71743,7 @@ exports.isAbsentProvider$1 = isAbsentProvider;
70424
71743
  exports.isByteUnit = isByteUnit;
70425
71744
  exports.isConsumableLevelField = isConsumableLevelField;
70426
71745
  exports.isDataTableFilterActive = isDataTableFilterActive;
71746
+ exports.isDeviceNameStock = isDeviceNameStock;
70427
71747
  Object.defineProperty(exports, "isDrawableSubjectPath", {
70428
71748
  enumerable: true,
70429
71749
  get: function() {
@@ -70465,6 +71785,7 @@ exports.nodeTextColorClass = nodeTextColorClass;
70465
71785
  exports.normalizeForSearch = normalizeForSearch;
70466
71786
  exports.normalizeLanguage = normalizeLanguage;
70467
71787
  exports.normalizeSelection = normalizeSelection;
71788
+ exports.nounSpecFor = nounSpecFor;
70468
71789
  exports.occupancyFacets = occupancyFacets;
70469
71790
  exports.occupancyLines = occupancyLines;
70470
71791
  exports.offListSelections = offListSelections;
@@ -70519,6 +71840,7 @@ exports.resolveContainerPrimary = resolveContainerPrimary;
70519
71840
  exports.resolveControlAlign = resolveControlAlign;
70520
71841
  exports.resolveDeviceControl = resolveDeviceControl;
70521
71842
  exports.resolveDeviceIntegration = resolveDeviceIntegration;
71843
+ exports.resolveDeviceLabel = resolveDeviceLabel;
70522
71844
  exports.resolveDisplayIcon = resolveDisplayIcon;
70523
71845
  exports.resolveEffectiveDefaultModel = resolveEffectiveDefaultModel;
70524
71846
  exports.resolveEffectiveStepModel = resolveEffectiveStepModel;
@@ -70537,6 +71859,7 @@ exports.rootSourceState = rootSourceState;
70537
71859
  exports.routerBasename = routerBasename;
70538
71860
  exports.runningDurationSince = runningDurationSince;
70539
71861
  exports.runningStatusParts = runningStatusParts;
71862
+ exports.sameDeviceName = sameDeviceName;
70540
71863
  exports.sameLocationSuggestions = sameLocationSuggestions;
70541
71864
  exports.scrubReducer = scrubReducer;
70542
71865
  exports.searchSteps = searchSteps;
@@ -70570,6 +71893,7 @@ exports.stepFieldDomId = stepFieldDomId;
70570
71893
  exports.stepHasModelForFormat = stepHasModelForFormat;
70571
71894
  exports.stepLocalFields = stepLocalFields;
70572
71895
  exports.stepModelOptions = stepModelOptions;
71896
+ exports.stripDeviceParentPrefix = stripDeviceParentPrefix;
70573
71897
  exports.stripParentNamePrefix = stripParentNamePrefix;
70574
71898
  Object.defineProperty(exports, "subjectPathSampleFromBox", {
70575
71899
  enumerable: true,
@@ -70867,6 +72191,8 @@ exports.useDeviceHistoryRecord = useDeviceHistoryRecord;
70867
72191
  exports.useDeviceHistoryRecordBuckets = useDeviceHistoryRecordBuckets;
70868
72192
  exports.useDeviceHistorySetDeviceEnabled = useDeviceHistorySetDeviceEnabled;
70869
72193
  exports.useDeviceId = useDeviceId;
72194
+ exports.useDeviceLabel = useDeviceLabel;
72195
+ exports.useDeviceLabelById = useDeviceLabelById;
70870
72196
  exports.useDeviceListPageSize = useDeviceListPageSize;
70871
72197
  exports.useDeviceManagerAddLocation = useDeviceManagerAddLocation;
70872
72198
  exports.useDeviceManagerAdoptDevice = useDeviceManagerAdoptDevice;
@@ -70960,6 +72286,8 @@ exports.useDeviceManagerTestField = useDeviceManagerTestField;
70960
72286
  exports.useDeviceManagerUpdateConfig = useDeviceManagerUpdateConfig;
70961
72287
  exports.useDeviceManagerUpdateDeviceField = useDeviceManagerUpdateDeviceField;
70962
72288
  exports.useDeviceManagerUpdateDeviceFieldsBatch = useDeviceManagerUpdateDeviceFieldsBatch;
72289
+ exports.useDeviceNameIndex = useDeviceNameIndex;
72290
+ exports.useDeviceNameNouns = useDeviceNameNouns;
70963
72291
  exports.useDeviceNetworkLink = useDeviceNetworkLink;
70964
72292
  exports.useDeviceOpsGetConfigEntries = useDeviceOpsGetConfigEntries;
70965
72293
  exports.useDeviceOpsGetRawState = useDeviceOpsGetRawState;