@camstack/ui-library 1.2.332 → 1.2.334

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) => {
@@ -11349,6 +11394,8 @@ var useMeshNetworkLogout = trpc.meshNetwork.logout.useMutation;
11349
11394
  var useMeshNetworkListPeers = trpc.meshNetwork.listPeers.useQuery;
11350
11395
  /** Generated alias around `trpc.meshNetwork.testConnection.useMutation`. */
11351
11396
  var useMeshNetworkTestConnection = trpc.meshNetwork.testConnection.useMutation;
11397
+ /** Generated alias around `trpc.meter.getStatus.useQuery`. */
11398
+ var useMeterGetStatus = trpc.meter.getStatus.useQuery;
11352
11399
  /** Generated alias around `trpc.metricsProvider.collectSnapshot.useQuery`. */
11353
11400
  var useMetricsProviderCollectSnapshot = trpc.metricsProvider.collectSnapshot.useQuery;
11354
11401
  /** Generated alias around `trpc.metricsProvider.getCached.useQuery`. */
@@ -19627,6 +19674,1151 @@ function AutotrackSection({ deviceId }) {
19627
19674
  });
19628
19675
  }
19629
19676
  //#endregion
19677
+ //#region src/composites/device-name/device-fleet.tsx
19678
+ /**
19679
+ * One slim fleet, shared. A row reads its parent from this index — never a
19680
+ * `getDevice` per row (the Customize bounded-read rule). `null` is "not
19681
+ * answered yet": a label then keeps the stored name, so a list does not flash
19682
+ * `#id` while the fleet is in flight. An empty array is an answer.
19683
+ */
19684
+ var DeviceNameIndexContext = (0, react$1.createContext)(null);
19685
+ function useDeviceNameIndex() {
19686
+ return (0, react$1.useContext)(DeviceNameIndexContext);
19687
+ }
19688
+ function copyDevice(device) {
19689
+ const label = device.display?.label;
19690
+ const trimmed = label === void 0 || label === null ? "" : label.trim();
19691
+ return {
19692
+ id: device.id,
19693
+ name: device.name,
19694
+ type: device.type,
19695
+ stableId: device.stableId,
19696
+ role: device.role ?? null,
19697
+ parentDeviceId: device.parentDeviceId ?? null,
19698
+ location: device.location ?? null,
19699
+ ...trimmed.length > 0 ? { display: { label: trimmed } } : {},
19700
+ ...device.primaryChildEntityId !== void 0 ? { primaryChildEntityId: device.primaryChildEntityId } : {},
19701
+ ...device.userNamed === void 0 ? {} : { userNamed: device.userNamed }
19702
+ };
19703
+ }
19704
+ /** Build the parent/child maps once per fleet answer. */
19705
+ function indexDeviceNames(devices) {
19706
+ const byId = /* @__PURE__ */ new Map();
19707
+ const children = /* @__PURE__ */ new Map();
19708
+ for (const device of devices) {
19709
+ const fields = copyDevice(device);
19710
+ byId.set(fields.id, fields);
19711
+ const parentId = fields.parentDeviceId;
19712
+ if (parentId === null || parentId === void 0 || parentId === fields.id) continue;
19713
+ const list = children.get(parentId);
19714
+ if (list === void 0) children.set(parentId, [fields.id]);
19715
+ else list.push(fields.id);
19716
+ }
19717
+ return {
19718
+ byId,
19719
+ childIds: children
19720
+ };
19721
+ }
19722
+ function DeviceFleetProvider({ devices, children }) {
19723
+ const index = (0, react$1.useMemo)(() => devices === null ? null : indexDeviceNames(devices), [devices]);
19724
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceNameIndexContext.Provider, {
19725
+ value: index,
19726
+ children
19727
+ });
19728
+ }
19729
+ /**
19730
+ * The one `deviceManager.listAll` subscriber for names. Same query key the
19731
+ * pickers already use, so a second observer does not fetch a second fleet.
19732
+ */
19733
+ function DeviceFleetFromServer({ children }) {
19734
+ const query = useDeviceManagerListAll({ projection: "slim" });
19735
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceFleetProvider, {
19736
+ devices: query.data === void 0 ? null : query.data,
19737
+ children
19738
+ });
19739
+ }
19740
+ //#endregion
19741
+ //#region src/composites/device-name/format-device-name.ts
19742
+ /**
19743
+ * One display name for a device.
19744
+ *
19745
+ * Canonical copy: `packages/ui-library/src/composites/device-name/format-device-name.ts`.
19746
+ * The viewer mirrors this file; `scripts/check-device-name-in-sync.ts` diffs the two.
19747
+ * Noun strings stay outside — this file only decides which noun to ask for.
19748
+ */
19749
+ /** Middle dot used between a container and its noun, and before a room. */
19750
+ var DEVICE_NAME_JOIN = " · ";
19751
+ var DEVICE_NAME_FORMS = [
19752
+ "compact",
19753
+ "full",
19754
+ "nested",
19755
+ "with-location"
19756
+ ];
19757
+ var PARENT_SEPARATORS = [" — ", " "];
19758
+ /**
19759
+ * Readings that are never what a container is. Matches the comment on
19760
+ * `CONTAINER_CHILD_ROLE_PRIORITY`: battery, temperature, humidity, illuminance,
19761
+ * signal, last-seen, plus numeric and enum. `update` is the same kind of row.
19762
+ */
19763
+ var DIAGNOSTIC_ROLES = new Set([
19764
+ "battery-sensor",
19765
+ "temperature-sensor",
19766
+ "humidity-sensor",
19767
+ "ambient-light-sensor",
19768
+ "connectivity-sensor",
19769
+ "datetime-sensor",
19770
+ "numeric-sensor",
19771
+ "enum-sensor"
19772
+ ]);
19773
+ /** Hyphen, underscore and whitespace fold together — the namesake comparison. */
19774
+ function foldDeviceName(value) {
19775
+ return value.toLowerCase().replace(/[-_\s]+/g, " ").trim();
19776
+ }
19777
+ function sameDeviceName(left, right) {
19778
+ return foldDeviceName(left) === foldDeviceName(right);
19779
+ }
19780
+ /**
19781
+ * Drop a parent prefix. `" — "` before a space, case-insensitive, so a provider
19782
+ * that capitalised the parent differently still strips, and an em-dash child
19783
+ * does not keep a dangling dash.
19784
+ */
19785
+ function stripDeviceParentPrefix(name, parentName) {
19786
+ if (parentName.length === 0) return name;
19787
+ const nameFold = name.toLowerCase();
19788
+ const parentFold = parentName.toLowerCase();
19789
+ for (const separator of PARENT_SEPARATORS) {
19790
+ const prefix = `${parentFold}${separator}`;
19791
+ if (!nameFold.startsWith(prefix)) continue;
19792
+ return name.slice(prefix.length).trim();
19793
+ }
19794
+ return name;
19795
+ }
19796
+ function roleOf(device) {
19797
+ const role = device.role;
19798
+ if (role === void 0 || role === null || role.length === 0) return null;
19799
+ return role;
19800
+ }
19801
+ function isDiagnostic(device) {
19802
+ if (device.type === "update") return true;
19803
+ const role = roleOf(device);
19804
+ return role !== null && DIAGNOSTIC_ROLES.has(role);
19805
+ }
19806
+ function displayLabel(device) {
19807
+ const label = device.display?.label;
19808
+ if (label === void 0 || label === null) return "";
19809
+ return label.trim();
19810
+ }
19811
+ function nounOf(device, parent, nouns) {
19812
+ const caption = displayLabel(device);
19813
+ if (caption.length > 0) return caption;
19814
+ const stripped = stripDeviceParentPrefix(device.name, parent.name);
19815
+ const shown = stripped.length > 0 ? stripped : device.name;
19816
+ if (device.userNamed === true) return shown;
19817
+ if (stripped.length === 0 || sameDeviceName(stripped, parent.name) || nouns.isStockPhrase(shown, roleOf(device), device.type)) return nouns.shortNoun(roleOf(device), device.type);
19818
+ return shown;
19819
+ }
19820
+ function foldsIntoParent(device, parent, noun) {
19821
+ if (sameDeviceName(noun, parent.name)) return true;
19822
+ if (displayLabel(device).length > 0 || device.userNamed === true) return false;
19823
+ return sameDeviceName(stripDeviceParentPrefix(device.name, parent.name), parent.name);
19824
+ }
19825
+ function meaningfulChildCount(siblings) {
19826
+ let count = 0;
19827
+ for (const sibling of siblings) if (!isDiagnostic(sibling)) count += 1;
19828
+ return count;
19829
+ }
19830
+ function isContainerPrimary(device, parent, siblings) {
19831
+ const picked = (0, _camstack_types_client.resolveContainerPrimaryChild)(siblings.map((sibling) => {
19832
+ const role = roleOf(sibling);
19833
+ return {
19834
+ id: sibling.id,
19835
+ stableId: sibling.stableId,
19836
+ type: sibling.type,
19837
+ name: sibling.name,
19838
+ ...role === null ? {} : { role }
19839
+ };
19840
+ }), parent.primaryChildEntityId, parent.name);
19841
+ return picked !== null && picked.id === device.id;
19842
+ }
19843
+ function childLine(device, parent, siblings, nouns, form) {
19844
+ const noun = nounOf(device, parent, nouns);
19845
+ if (foldsIntoParent(device, parent, noun)) return parent.name;
19846
+ if (form === "compact" && parent.type === "container" && isContainerPrimary(device, parent, siblings) && meaningfulChildCount(siblings) === 1) return parent.name;
19847
+ return `${parent.name}${DEVICE_NAME_JOIN}${noun}`;
19848
+ }
19849
+ function joinLocation(label, location) {
19850
+ if (location === void 0 || location === null) return label;
19851
+ const room = location.trim();
19852
+ if (room.length === 0) return label;
19853
+ if (label.split(" · ").some((segment) => sameDeviceName(segment, room))) return label;
19854
+ return `${label}${DEVICE_NAME_JOIN}${room}`;
19855
+ }
19856
+ function hasParent(device, parent) {
19857
+ if (parent === null) return false;
19858
+ const parentId = device.parentDeviceId;
19859
+ return parentId !== null && parentId !== void 0;
19860
+ }
19861
+ /**
19862
+ * The label for `form`. A top-level device keeps its stored name. A child uses
19863
+ * the noun: `compact` may stand in for the container when this child is what
19864
+ * the container is, `full` always names both unless they fold together,
19865
+ * `nested` is the noun alone, `with-location` is `compact` plus the room.
19866
+ */
19867
+ function formatDeviceName(device, form, context, nouns) {
19868
+ if (!hasParent(device, context.parent)) return form === "with-location" ? joinLocation(device.name, device.location) : device.name;
19869
+ if (form === "nested") return nounOf(device, context.parent, nouns);
19870
+ const line = childLine(device, context.parent, context.siblings, nouns, form === "full" ? "full" : "compact");
19871
+ return form === "with-location" ? joinLocation(line, device.location) : line;
19872
+ }
19873
+ //#endregion
19874
+ //#region src/composites/device-name/device-name-nouns.ts
19875
+ /**
19876
+ * Short nouns and the stock words they replace.
19877
+ *
19878
+ * Long labels are the existing role and type names, both locales, so a stored
19879
+ * "Motion sensor" and a stored "Sensore di movimento" are the same stock word.
19880
+ * The short noun is what a list shows. Where the long label is already one
19881
+ * word it is the short noun. Aliases are provider stock words that are not the
19882
+ * label ("Illumination", "Last seen").
19883
+ */
19884
+ var DEVICE_ROLE_NOUNS = [
19885
+ {
19886
+ id: "siren",
19887
+ key: "uiLibrary.deviceMeta.noun.role.siren",
19888
+ en: "Siren",
19889
+ it: "Sirena",
19890
+ shortEn: "Siren",
19891
+ shortIt: "Sirena",
19892
+ aliases: []
19893
+ },
19894
+ {
19895
+ id: "floodlight",
19896
+ key: "uiLibrary.deviceMeta.noun.role.floodlight",
19897
+ en: "Floodlight",
19898
+ it: "Faro",
19899
+ shortEn: "Floodlight",
19900
+ shortIt: "Faro",
19901
+ aliases: []
19902
+ },
19903
+ {
19904
+ id: "spotlight",
19905
+ key: "uiLibrary.deviceMeta.noun.role.spotlight",
19906
+ en: "Spotlight",
19907
+ it: "Faretto",
19908
+ shortEn: "Spotlight",
19909
+ shortIt: "Faretto",
19910
+ aliases: []
19911
+ },
19912
+ {
19913
+ id: "pir-sensor",
19914
+ key: "uiLibrary.deviceMeta.noun.role.pirSensor",
19915
+ en: "PIR sensor",
19916
+ it: "Sensore PIR",
19917
+ shortEn: "PIR",
19918
+ shortIt: "PIR",
19919
+ aliases: []
19920
+ },
19921
+ {
19922
+ id: "chime",
19923
+ key: "uiLibrary.deviceMeta.noun.role.chime",
19924
+ en: "Chime",
19925
+ it: "Campanello",
19926
+ shortEn: "Chime",
19927
+ shortIt: "Campanello",
19928
+ aliases: []
19929
+ },
19930
+ {
19931
+ id: "autotrack",
19932
+ key: "uiLibrary.deviceMeta.noun.role.autotrack",
19933
+ en: "Auto-track",
19934
+ it: "Auto-tracking",
19935
+ shortEn: "Auto-track",
19936
+ shortIt: "Auto-tracking",
19937
+ aliases: []
19938
+ },
19939
+ {
19940
+ id: "nightvision",
19941
+ key: "uiLibrary.deviceMeta.noun.role.nightvision",
19942
+ en: "Night vision",
19943
+ it: "Visione notturna",
19944
+ shortEn: "Night",
19945
+ shortIt: "Notturna",
19946
+ aliases: []
19947
+ },
19948
+ {
19949
+ id: "privacy-mask",
19950
+ key: "uiLibrary.deviceMeta.noun.role.privacyMask",
19951
+ en: "Privacy mask",
19952
+ it: "Maschera privacy",
19953
+ shortEn: "Privacy",
19954
+ shortIt: "Privacy",
19955
+ aliases: []
19956
+ },
19957
+ {
19958
+ id: "doorbell",
19959
+ key: "uiLibrary.deviceMeta.noun.role.doorbell",
19960
+ en: "Doorbell",
19961
+ it: "Campanello video",
19962
+ shortEn: "Doorbell",
19963
+ shortIt: "Campanello video",
19964
+ aliases: []
19965
+ },
19966
+ {
19967
+ id: "cover-blind",
19968
+ key: "uiLibrary.deviceMeta.noun.role.coverBlind",
19969
+ en: "Blind",
19970
+ it: "Tapparella",
19971
+ shortEn: "Blind",
19972
+ shortIt: "Tapparella",
19973
+ aliases: []
19974
+ },
19975
+ {
19976
+ id: "cover-shutter",
19977
+ key: "uiLibrary.deviceMeta.noun.role.coverShutter",
19978
+ en: "Shutter",
19979
+ it: "Persiana",
19980
+ shortEn: "Shutter",
19981
+ shortIt: "Persiana",
19982
+ aliases: []
19983
+ },
19984
+ {
19985
+ id: "cover-awning",
19986
+ key: "uiLibrary.deviceMeta.noun.role.coverAwning",
19987
+ en: "Awning",
19988
+ it: "Tenda da sole",
19989
+ shortEn: "Awning",
19990
+ shortIt: "Tenda da sole",
19991
+ aliases: []
19992
+ },
19993
+ {
19994
+ id: "cover-curtain",
19995
+ key: "uiLibrary.deviceMeta.noun.role.coverCurtain",
19996
+ en: "Curtain",
19997
+ it: "Tenda",
19998
+ shortEn: "Curtain",
19999
+ shortIt: "Tenda",
20000
+ aliases: []
20001
+ },
20002
+ {
20003
+ id: "cover-garage",
20004
+ key: "uiLibrary.deviceMeta.noun.role.coverGarage",
20005
+ en: "Garage door",
20006
+ it: "Porta del garage",
20007
+ shortEn: "Garage",
20008
+ shortIt: "Garage",
20009
+ aliases: []
20010
+ },
20011
+ {
20012
+ id: "cover-gate",
20013
+ key: "uiLibrary.deviceMeta.noun.role.coverGate",
20014
+ en: "Gate",
20015
+ it: "Cancello",
20016
+ shortEn: "Gate",
20017
+ shortIt: "Cancello",
20018
+ aliases: []
20019
+ },
20020
+ {
20021
+ id: "cover-door",
20022
+ key: "uiLibrary.deviceMeta.noun.role.coverDoor",
20023
+ en: "Door",
20024
+ it: "Porta",
20025
+ shortEn: "Door",
20026
+ shortIt: "Porta",
20027
+ aliases: []
20028
+ },
20029
+ {
20030
+ id: "cover-window",
20031
+ key: "uiLibrary.deviceMeta.noun.role.coverWindow",
20032
+ en: "Window",
20033
+ it: "Finestra",
20034
+ shortEn: "Window",
20035
+ shortIt: "Finestra",
20036
+ aliases: []
20037
+ },
20038
+ {
20039
+ id: "binary-helper",
20040
+ key: "uiLibrary.deviceMeta.noun.role.binaryHelper",
20041
+ en: "Binary helper",
20042
+ it: "Helper binario",
20043
+ shortEn: "Helper",
20044
+ shortIt: "Helper",
20045
+ aliases: []
20046
+ },
20047
+ {
20048
+ id: "outlet",
20049
+ key: "uiLibrary.deviceMeta.noun.role.outlet",
20050
+ en: "Outlet",
20051
+ it: "Presa",
20052
+ shortEn: "Outlet",
20053
+ shortIt: "Presa",
20054
+ aliases: []
20055
+ },
20056
+ {
20057
+ id: "motion-sensor",
20058
+ key: "uiLibrary.deviceMeta.noun.role.motionSensor",
20059
+ en: "Motion sensor",
20060
+ it: "Sensore di movimento",
20061
+ shortEn: "Motion",
20062
+ shortIt: "Movimento",
20063
+ aliases: []
20064
+ },
20065
+ {
20066
+ id: "contact-sensor",
20067
+ key: "uiLibrary.deviceMeta.noun.role.contactSensor",
20068
+ en: "Contact sensor",
20069
+ it: "Sensore di contatto",
20070
+ shortEn: "Contact",
20071
+ shortIt: "Contatto",
20072
+ aliases: []
20073
+ },
20074
+ {
20075
+ id: "leak-sensor",
20076
+ key: "uiLibrary.deviceMeta.noun.role.leakSensor",
20077
+ en: "Leak sensor",
20078
+ it: "Sensore di perdite",
20079
+ shortEn: "Leak",
20080
+ shortIt: "Perdita",
20081
+ aliases: []
20082
+ },
20083
+ {
20084
+ id: "smoke-sensor",
20085
+ key: "uiLibrary.deviceMeta.noun.role.smokeSensor",
20086
+ en: "Smoke sensor",
20087
+ it: "Sensore di fumo",
20088
+ shortEn: "Smoke",
20089
+ shortIt: "Fumo",
20090
+ aliases: []
20091
+ },
20092
+ {
20093
+ id: "co-sensor",
20094
+ key: "uiLibrary.deviceMeta.noun.role.cOSensor",
20095
+ en: "CO sensor",
20096
+ it: "Sensore di CO",
20097
+ shortEn: "CO",
20098
+ shortIt: "CO",
20099
+ aliases: []
20100
+ },
20101
+ {
20102
+ id: "gas-sensor",
20103
+ key: "uiLibrary.deviceMeta.noun.role.gasSensor",
20104
+ en: "Gas sensor",
20105
+ it: "Sensore di gas",
20106
+ shortEn: "Gas",
20107
+ shortIt: "Gas",
20108
+ aliases: []
20109
+ },
20110
+ {
20111
+ id: "tamper-sensor",
20112
+ key: "uiLibrary.deviceMeta.noun.role.tamperSensor",
20113
+ en: "Tamper sensor",
20114
+ it: "Sensore antimanomissione",
20115
+ shortEn: "Tamper",
20116
+ shortIt: "Manomissione",
20117
+ aliases: []
20118
+ },
20119
+ {
20120
+ id: "vibration-sensor",
20121
+ key: "uiLibrary.deviceMeta.noun.role.vibrationSensor",
20122
+ en: "Vibration sensor",
20123
+ it: "Sensore di vibrazione",
20124
+ shortEn: "Vibration",
20125
+ shortIt: "Vibrazione",
20126
+ aliases: []
20127
+ },
20128
+ {
20129
+ id: "connectivity-sensor",
20130
+ key: "uiLibrary.deviceMeta.noun.role.connectivitySensor",
20131
+ en: "Connectivity",
20132
+ it: "Connettività",
20133
+ shortEn: "Connectivity",
20134
+ shortIt: "Connettività",
20135
+ aliases: []
20136
+ },
20137
+ {
20138
+ id: "sound-sensor",
20139
+ key: "uiLibrary.deviceMeta.noun.role.soundSensor",
20140
+ en: "Sound sensor",
20141
+ it: "Sensore acustico",
20142
+ shortEn: "Sound",
20143
+ shortIt: "Suono",
20144
+ aliases: []
20145
+ },
20146
+ {
20147
+ id: "binary-sensor",
20148
+ key: "uiLibrary.deviceMeta.noun.role.binarySensor",
20149
+ en: "Binary sensor",
20150
+ it: "Sensore binario",
20151
+ shortEn: "Binary",
20152
+ shortIt: "Binario",
20153
+ aliases: []
20154
+ },
20155
+ {
20156
+ id: "temperature-sensor",
20157
+ key: "uiLibrary.deviceMeta.noun.role.temperatureSensor",
20158
+ en: "Temperature",
20159
+ it: "Temperatura",
20160
+ shortEn: "Temperature",
20161
+ shortIt: "Temperatura",
20162
+ aliases: []
20163
+ },
20164
+ {
20165
+ id: "humidity-sensor",
20166
+ key: "uiLibrary.deviceMeta.noun.role.humiditySensor",
20167
+ en: "Humidity",
20168
+ it: "Umidità",
20169
+ shortEn: "Humidity",
20170
+ shortIt: "Umidità",
20171
+ aliases: []
20172
+ },
20173
+ {
20174
+ id: "ambient-light-sensor",
20175
+ key: "uiLibrary.deviceMeta.noun.role.ambientLightSensor",
20176
+ en: "Ambient light",
20177
+ it: "Luce ambientale",
20178
+ shortEn: "Light",
20179
+ shortIt: "Luce",
20180
+ aliases: ["Illumination"]
20181
+ },
20182
+ {
20183
+ id: "pressure-sensor",
20184
+ key: "uiLibrary.deviceMeta.noun.role.pressureSensor",
20185
+ en: "Pressure",
20186
+ it: "Pressione",
20187
+ shortEn: "Pressure",
20188
+ shortIt: "Pressione",
20189
+ aliases: []
20190
+ },
20191
+ {
20192
+ id: "wind-sensor",
20193
+ key: "uiLibrary.deviceMeta.noun.role.windSensor",
20194
+ en: "Wind",
20195
+ it: "Vento",
20196
+ shortEn: "Wind",
20197
+ shortIt: "Vento",
20198
+ aliases: []
20199
+ },
20200
+ {
20201
+ id: "rain-sensor",
20202
+ key: "uiLibrary.deviceMeta.noun.role.rainSensor",
20203
+ en: "Rain",
20204
+ it: "Pioggia",
20205
+ shortEn: "Rain",
20206
+ shortIt: "Pioggia",
20207
+ aliases: []
20208
+ },
20209
+ {
20210
+ id: "uv-sensor",
20211
+ key: "uiLibrary.deviceMeta.noun.role.uvSensor",
20212
+ en: "UV index",
20213
+ it: "Indice UV",
20214
+ shortEn: "UV index",
20215
+ shortIt: "Indice UV",
20216
+ aliases: []
20217
+ },
20218
+ {
20219
+ id: "solar-radiation-sensor",
20220
+ key: "uiLibrary.deviceMeta.noun.role.solarRadiationSensor",
20221
+ en: "Solar radiation",
20222
+ it: "Radiazione solare",
20223
+ shortEn: "Solar radiation",
20224
+ shortIt: "Radiazione solare",
20225
+ aliases: []
20226
+ },
20227
+ {
20228
+ id: "soil-moisture-sensor",
20229
+ key: "uiLibrary.deviceMeta.noun.role.soilMoistureSensor",
20230
+ en: "Soil moisture",
20231
+ it: "Umidità del suolo",
20232
+ shortEn: "Soil moisture",
20233
+ shortIt: "Umidità del suolo",
20234
+ aliases: []
20235
+ },
20236
+ {
20237
+ id: "power-sensor",
20238
+ key: "uiLibrary.deviceMeta.noun.role.powerSensor",
20239
+ en: "Power",
20240
+ it: "Potenza",
20241
+ shortEn: "Power",
20242
+ shortIt: "Potenza",
20243
+ aliases: []
20244
+ },
20245
+ {
20246
+ id: "energy-sensor",
20247
+ key: "uiLibrary.deviceMeta.noun.role.energySensor",
20248
+ en: "Energy",
20249
+ it: "Energia",
20250
+ shortEn: "Energy",
20251
+ shortIt: "Energia",
20252
+ aliases: []
20253
+ },
20254
+ {
20255
+ id: "voltage-sensor",
20256
+ key: "uiLibrary.deviceMeta.noun.role.voltageSensor",
20257
+ en: "Voltage",
20258
+ it: "Tensione",
20259
+ shortEn: "Voltage",
20260
+ shortIt: "Tensione",
20261
+ aliases: []
20262
+ },
20263
+ {
20264
+ id: "current-sensor",
20265
+ key: "uiLibrary.deviceMeta.noun.role.currentSensor",
20266
+ en: "Current",
20267
+ it: "Corrente",
20268
+ shortEn: "Current",
20269
+ shortIt: "Corrente",
20270
+ aliases: []
20271
+ },
20272
+ {
20273
+ id: "air-quality-sensor",
20274
+ key: "uiLibrary.deviceMeta.noun.role.airQualitySensor",
20275
+ en: "Air quality",
20276
+ it: "Qualità dell’aria",
20277
+ shortEn: "Air quality",
20278
+ shortIt: "Qualità dell’aria",
20279
+ aliases: []
20280
+ },
20281
+ {
20282
+ id: "battery-sensor",
20283
+ key: "uiLibrary.deviceMeta.noun.role.batterySensor",
20284
+ en: "Battery",
20285
+ it: "Batteria",
20286
+ shortEn: "Battery",
20287
+ shortIt: "Batteria",
20288
+ aliases: []
20289
+ },
20290
+ {
20291
+ id: "numeric-sensor",
20292
+ key: "uiLibrary.deviceMeta.noun.role.numericSensor",
20293
+ en: "Numeric sensor",
20294
+ it: "Sensore numerico",
20295
+ shortEn: "Value",
20296
+ shortIt: "Numerico",
20297
+ aliases: []
20298
+ },
20299
+ {
20300
+ id: "enum-sensor",
20301
+ key: "uiLibrary.deviceMeta.noun.role.enumSensor",
20302
+ en: "Enum sensor",
20303
+ it: "Sensore a stati",
20304
+ shortEn: "State",
20305
+ shortIt: "Stato",
20306
+ aliases: []
20307
+ },
20308
+ {
20309
+ id: "datetime-sensor",
20310
+ key: "uiLibrary.deviceMeta.noun.role.dateTimeSensor",
20311
+ en: "Date / time",
20312
+ it: "Data / ora",
20313
+ shortEn: "Time",
20314
+ shortIt: "Ora",
20315
+ aliases: ["Last seen"]
20316
+ },
20317
+ {
20318
+ id: "generic-sensor",
20319
+ key: "uiLibrary.deviceMeta.noun.role.genericSensor",
20320
+ en: "Sensor",
20321
+ it: "Sensore",
20322
+ shortEn: "Sensor",
20323
+ shortIt: "Sensore",
20324
+ aliases: []
20325
+ },
20326
+ {
20327
+ id: "numeric-control",
20328
+ key: "uiLibrary.deviceMeta.noun.role.numericControl",
20329
+ en: "Numeric control",
20330
+ it: "Controllo numerico",
20331
+ shortEn: "Number",
20332
+ shortIt: "Numero",
20333
+ aliases: []
20334
+ },
20335
+ {
20336
+ id: "select-control",
20337
+ key: "uiLibrary.deviceMeta.noun.role.selectControl",
20338
+ en: "Select control",
20339
+ it: "Controllo a selezione",
20340
+ shortEn: "Select",
20341
+ shortIt: "Selezione",
20342
+ aliases: []
20343
+ },
20344
+ {
20345
+ id: "text-control",
20346
+ key: "uiLibrary.deviceMeta.noun.role.textControl",
20347
+ en: "Text control",
20348
+ it: "Controllo testo",
20349
+ shortEn: "Text",
20350
+ shortIt: "Testo",
20351
+ aliases: []
20352
+ },
20353
+ {
20354
+ id: "datetime-control",
20355
+ key: "uiLibrary.deviceMeta.noun.role.dateTimeControl",
20356
+ en: "Date / time control",
20357
+ it: "Controllo data / ora",
20358
+ shortEn: "Time",
20359
+ shortIt: "Ora",
20360
+ aliases: []
20361
+ },
20362
+ {
20363
+ id: "mobile-push-notifier",
20364
+ key: "uiLibrary.deviceMeta.noun.role.mobilePushNotifier",
20365
+ en: "Mobile push",
20366
+ it: "Push su mobile",
20367
+ shortEn: "Push",
20368
+ shortIt: "Push",
20369
+ aliases: []
20370
+ },
20371
+ {
20372
+ id: "messaging-notifier",
20373
+ key: "uiLibrary.deviceMeta.noun.role.messagingNotifier",
20374
+ en: "Messaging",
20375
+ it: "Messaggistica",
20376
+ shortEn: "Messaging",
20377
+ shortIt: "Messaggistica",
20378
+ aliases: []
20379
+ },
20380
+ {
20381
+ id: "email-notifier",
20382
+ key: "uiLibrary.deviceMeta.noun.role.emailNotifier",
20383
+ en: "Email",
20384
+ it: "Email",
20385
+ shortEn: "Email",
20386
+ shortIt: "Email",
20387
+ aliases: []
20388
+ },
20389
+ {
20390
+ id: "generic-notifier",
20391
+ key: "uiLibrary.deviceMeta.noun.role.genericNotifier",
20392
+ en: "Notifier",
20393
+ it: "Notificatore",
20394
+ shortEn: "Notifier",
20395
+ shortIt: "Notificatore",
20396
+ aliases: []
20397
+ }
20398
+ ];
20399
+ var DEVICE_TYPE_NOUNS = [
20400
+ {
20401
+ id: "camera",
20402
+ key: "uiLibrary.deviceMeta.noun.type.camera",
20403
+ en: "Camera",
20404
+ it: "Telecamera",
20405
+ shortEn: "Camera",
20406
+ shortIt: "Telecamera",
20407
+ aliases: []
20408
+ },
20409
+ {
20410
+ id: "hub",
20411
+ key: "uiLibrary.deviceMeta.noun.type.hub",
20412
+ en: "Hub",
20413
+ it: "Hub",
20414
+ shortEn: "Hub",
20415
+ shortIt: "Hub",
20416
+ aliases: []
20417
+ },
20418
+ {
20419
+ id: "light",
20420
+ key: "uiLibrary.deviceMeta.noun.type.light",
20421
+ en: "Light",
20422
+ it: "Luce",
20423
+ shortEn: "Light",
20424
+ shortIt: "Luce",
20425
+ aliases: []
20426
+ },
20427
+ {
20428
+ id: "siren",
20429
+ key: "uiLibrary.deviceMeta.noun.type.siren",
20430
+ en: "Siren",
20431
+ it: "Sirena",
20432
+ shortEn: "Siren",
20433
+ shortIt: "Sirena",
20434
+ aliases: []
20435
+ },
20436
+ {
20437
+ id: "switch",
20438
+ key: "uiLibrary.deviceMeta.noun.type.switch",
20439
+ en: "Switch",
20440
+ it: "Interruttore",
20441
+ shortEn: "Switch",
20442
+ shortIt: "Interruttore",
20443
+ aliases: []
20444
+ },
20445
+ {
20446
+ id: "sensor",
20447
+ key: "uiLibrary.deviceMeta.noun.type.sensor",
20448
+ en: "Sensor",
20449
+ it: "Sensore",
20450
+ shortEn: "Sensor",
20451
+ shortIt: "Sensore",
20452
+ aliases: []
20453
+ },
20454
+ {
20455
+ id: "thermostat",
20456
+ key: "uiLibrary.deviceMeta.noun.type.thermostat",
20457
+ en: "Thermostat",
20458
+ it: "Termostato",
20459
+ shortEn: "Thermostat",
20460
+ shortIt: "Termostato",
20461
+ aliases: []
20462
+ },
20463
+ {
20464
+ id: "climate",
20465
+ key: "uiLibrary.deviceMeta.noun.type.climate",
20466
+ en: "Air conditioner",
20467
+ it: "Condizionatore",
20468
+ shortEn: "Climate",
20469
+ shortIt: "Condizionatore",
20470
+ aliases: []
20471
+ },
20472
+ {
20473
+ id: "button",
20474
+ key: "uiLibrary.deviceMeta.noun.type.button",
20475
+ en: "Button",
20476
+ it: "Pulsante",
20477
+ shortEn: "Button",
20478
+ shortIt: "Pulsante",
20479
+ aliases: []
20480
+ },
20481
+ {
20482
+ id: "event-emitter",
20483
+ key: "uiLibrary.deviceMeta.noun.type.eventEmitter",
20484
+ en: "Event Emitter",
20485
+ it: "Emettitore di eventi",
20486
+ shortEn: "Event",
20487
+ shortIt: "Evento",
20488
+ aliases: []
20489
+ },
20490
+ {
20491
+ id: "update",
20492
+ key: "uiLibrary.deviceMeta.noun.type.update",
20493
+ en: "Update",
20494
+ it: "Aggiornamento",
20495
+ shortEn: "Update",
20496
+ shortIt: "Aggiornamento",
20497
+ aliases: []
20498
+ },
20499
+ {
20500
+ id: "generic",
20501
+ key: "uiLibrary.deviceMeta.noun.type.generic",
20502
+ en: "Generic",
20503
+ it: "Generico",
20504
+ shortEn: "Generic",
20505
+ shortIt: "Generico",
20506
+ aliases: []
20507
+ },
20508
+ {
20509
+ id: "notifier",
20510
+ key: "uiLibrary.deviceMeta.noun.type.notifier",
20511
+ en: "Notifier",
20512
+ it: "Notificatore",
20513
+ shortEn: "Notifier",
20514
+ shortIt: "Notificatore",
20515
+ aliases: []
20516
+ },
20517
+ {
20518
+ id: "script",
20519
+ key: "uiLibrary.deviceMeta.noun.type.script",
20520
+ en: "Script",
20521
+ it: "Script",
20522
+ shortEn: "Script",
20523
+ shortIt: "Script",
20524
+ aliases: []
20525
+ },
20526
+ {
20527
+ id: "automation",
20528
+ key: "uiLibrary.deviceMeta.noun.type.automation",
20529
+ en: "Automation",
20530
+ it: "Automazione",
20531
+ shortEn: "Automation",
20532
+ shortIt: "Automazione",
20533
+ aliases: []
20534
+ },
20535
+ {
20536
+ id: "lock",
20537
+ key: "uiLibrary.deviceMeta.noun.type.lock",
20538
+ en: "Lock",
20539
+ it: "Serratura",
20540
+ shortEn: "Lock",
20541
+ shortIt: "Serratura",
20542
+ aliases: []
20543
+ },
20544
+ {
20545
+ id: "cover",
20546
+ key: "uiLibrary.deviceMeta.noun.type.cover",
20547
+ en: "Cover",
20548
+ it: "Copertura",
20549
+ shortEn: "Cover",
20550
+ shortIt: "Copertura",
20551
+ aliases: []
20552
+ },
20553
+ {
20554
+ id: "valve",
20555
+ key: "uiLibrary.deviceMeta.noun.type.valve",
20556
+ en: "Valve",
20557
+ it: "Valvola",
20558
+ shortEn: "Valve",
20559
+ shortIt: "Valvola",
20560
+ aliases: []
20561
+ },
20562
+ {
20563
+ id: "humidifier",
20564
+ key: "uiLibrary.deviceMeta.noun.type.humidifier",
20565
+ en: "Humidifier",
20566
+ it: "Umidificatore",
20567
+ shortEn: "Humidifier",
20568
+ shortIt: "Umidificatore",
20569
+ aliases: []
20570
+ },
20571
+ {
20572
+ id: "water-heater",
20573
+ key: "uiLibrary.deviceMeta.noun.type.waterHeater",
20574
+ en: "Water heater",
20575
+ it: "Scaldabagno",
20576
+ shortEn: "Heater",
20577
+ shortIt: "Scaldabagno",
20578
+ aliases: []
20579
+ },
20580
+ {
20581
+ id: "fan",
20582
+ key: "uiLibrary.deviceMeta.noun.type.fan",
20583
+ en: "Fan",
20584
+ it: "Ventilatore",
20585
+ shortEn: "Fan",
20586
+ shortIt: "Ventilatore",
20587
+ aliases: []
20588
+ },
20589
+ {
20590
+ id: "media-player",
20591
+ key: "uiLibrary.deviceMeta.noun.type.mediaPlayer",
20592
+ en: "Media player",
20593
+ it: "Lettore multimediale",
20594
+ shortEn: "Player",
20595
+ shortIt: "Lettore",
20596
+ aliases: []
20597
+ },
20598
+ {
20599
+ id: "alarm-panel",
20600
+ key: "uiLibrary.deviceMeta.noun.type.alarmPanel",
20601
+ en: "Alarm panel",
20602
+ it: "Pannello allarme",
20603
+ shortEn: "Alarm",
20604
+ shortIt: "Allarme",
20605
+ aliases: []
20606
+ },
20607
+ {
20608
+ id: "control",
20609
+ key: "uiLibrary.deviceMeta.noun.type.control",
20610
+ en: "Control",
20611
+ it: "Controllo",
20612
+ shortEn: "Control",
20613
+ shortIt: "Controllo",
20614
+ aliases: []
20615
+ },
20616
+ {
20617
+ id: "presence",
20618
+ key: "uiLibrary.deviceMeta.noun.type.presence",
20619
+ en: "Presence",
20620
+ it: "Presenza",
20621
+ shortEn: "Presence",
20622
+ shortIt: "Presenza",
20623
+ aliases: []
20624
+ },
20625
+ {
20626
+ id: "weather",
20627
+ key: "uiLibrary.deviceMeta.noun.type.weather",
20628
+ en: "Weather",
20629
+ it: "Meteo",
20630
+ shortEn: "Weather",
20631
+ shortIt: "Meteo",
20632
+ aliases: []
20633
+ },
20634
+ {
20635
+ id: "vacuum",
20636
+ key: "uiLibrary.deviceMeta.noun.type.vacuum",
20637
+ en: "Vacuum",
20638
+ it: "Aspirapolvere",
20639
+ shortEn: "Vacuum",
20640
+ shortIt: "Aspirapolvere",
20641
+ aliases: []
20642
+ },
20643
+ {
20644
+ id: "lawn-mower",
20645
+ key: "uiLibrary.deviceMeta.noun.type.lawnMower",
20646
+ en: "Lawn mower",
20647
+ it: "Tagliaerba",
20648
+ shortEn: "Mower",
20649
+ shortIt: "Tagliaerba",
20650
+ aliases: []
20651
+ },
20652
+ {
20653
+ id: "container",
20654
+ key: "uiLibrary.deviceMeta.noun.type.container",
20655
+ en: "Container",
20656
+ it: "Contenitore",
20657
+ shortEn: "Container",
20658
+ shortIt: "Contenitore",
20659
+ aliases: []
20660
+ },
20661
+ {
20662
+ id: "image",
20663
+ key: "uiLibrary.deviceMeta.noun.type.image",
20664
+ en: "Image",
20665
+ it: "Immagine",
20666
+ shortEn: "Image",
20667
+ shortIt: "Immagine",
20668
+ aliases: []
20669
+ },
20670
+ {
20671
+ id: "pet-feeder",
20672
+ key: "uiLibrary.deviceMeta.noun.type.petFeeder",
20673
+ en: "Pet feeder",
20674
+ it: "Distributore di cibo per animali",
20675
+ shortEn: "Feeder",
20676
+ shortIt: "Distributore",
20677
+ aliases: []
20678
+ },
20679
+ {
20680
+ id: "dishwasher",
20681
+ key: "uiLibrary.deviceMeta.noun.type.dishwasher",
20682
+ en: "Dishwasher",
20683
+ it: "Lavastoviglie",
20684
+ shortEn: "Dishwasher",
20685
+ shortIt: "Lavastoviglie",
20686
+ aliases: []
20687
+ }
20688
+ ];
20689
+ var FALLBACK_NOUN = {
20690
+ id: "",
20691
+ key: "uiLibrary.deviceMeta.noun.fallback",
20692
+ en: "Device",
20693
+ it: "Dispositivo",
20694
+ shortEn: "Device",
20695
+ shortIt: "Dispositivo",
20696
+ aliases: []
20697
+ };
20698
+ function findNoun(list, id) {
20699
+ if (id === null || id.length === 0) return void 0;
20700
+ return list.find((spec) => spec.id === id);
20701
+ }
20702
+ /** Role wins when it has a noun; otherwise the type. Unknown types fall back. */
20703
+ function nounSpecFor(role, type) {
20704
+ if (role !== null) {
20705
+ const byRole = findNoun(DEVICE_ROLE_NOUNS, role);
20706
+ if (byRole !== void 0) return byRole;
20707
+ }
20708
+ return findNoun(DEVICE_TYPE_NOUNS, type) ?? FALLBACK_NOUN;
20709
+ }
20710
+ function stockPhrases(role, type) {
20711
+ const phrases = [];
20712
+ const specs = [findNoun(DEVICE_ROLE_NOUNS, role), findNoun(DEVICE_TYPE_NOUNS, type)];
20713
+ for (const spec of specs) {
20714
+ if (spec === void 0) continue;
20715
+ phrases.push(spec.en, spec.it, spec.shortEn, spec.shortIt, ...spec.aliases);
20716
+ }
20717
+ return phrases;
20718
+ }
20719
+ function isDeviceNameStock(text, role, type) {
20720
+ const folded = foldDeviceName(text);
20721
+ if (folded.length === 0) return true;
20722
+ return stockPhrases(role, type).some((phrase) => foldDeviceName(phrase) === folded);
20723
+ }
20724
+ /** Noun table for a locale. Tests and the viewer parity spec use this directly. */
20725
+ function deviceNameNouns(locale) {
20726
+ return {
20727
+ shortNoun(role, type) {
20728
+ const spec = nounSpecFor(role, type);
20729
+ return locale === "it" ? spec.shortIt : spec.shortEn;
20730
+ },
20731
+ isStockPhrase(text, role, type) {
20732
+ return isDeviceNameStock(text, role, type);
20733
+ }
20734
+ };
20735
+ }
20736
+ //#endregion
20737
+ //#region src/composites/device-name/use-device-label.ts
20738
+ /**
20739
+ * `useDeviceLabel` is the string `DeviceName` renders — sort keys, aria
20740
+ * labels, confirm copy and search titles call it instead of painting
20741
+ * `device.name`.
20742
+ */
20743
+ function useDeviceNameNouns() {
20744
+ const t = useUiText();
20745
+ return {
20746
+ shortNoun(role, type) {
20747
+ const spec = nounSpecFor(role, type);
20748
+ return t(spec.key, spec.shortEn);
20749
+ },
20750
+ isStockPhrase(text, role, type) {
20751
+ return isDeviceNameStock(text, role, type);
20752
+ }
20753
+ };
20754
+ }
20755
+ function placeholderParent(id) {
20756
+ return {
20757
+ id,
20758
+ name: `#${String(id)}`,
20759
+ type: "unknown",
20760
+ stableId: "",
20761
+ role: null,
20762
+ parentDeviceId: null
20763
+ };
20764
+ }
20765
+ function parentFromIndex(device, index) {
20766
+ const parentId = device.parentDeviceId;
20767
+ if (parentId === null || parentId === void 0) return null;
20768
+ return index.byId.get(parentId) ?? placeholderParent(parentId);
20769
+ }
20770
+ function siblingsFromIndex(device, index) {
20771
+ const parentId = device.parentDeviceId;
20772
+ if (parentId === null || parentId === void 0) return [];
20773
+ const ids = index.childIds.get(parentId) ?? [];
20774
+ const siblings = [];
20775
+ for (const id of ids) {
20776
+ const row = index.byId.get(id);
20777
+ if (row !== void 0) siblings.push(row);
20778
+ }
20779
+ if (!siblings.some((sibling) => sibling.id === device.id)) siblings.push(device);
20780
+ return siblings;
20781
+ }
20782
+ /**
20783
+ * Label for a device the caller already holds. While the fleet has not
20784
+ * answered and no parent was passed, this is the stored name — not `#id`.
20785
+ */
20786
+ function resolveDeviceLabel(device, form, index, nouns, scope) {
20787
+ const subject = index?.byId.get(device.id) ?? device;
20788
+ const parentId = subject.parentDeviceId;
20789
+ if (parentId !== null && parentId !== void 0 && index === null && scope?.parent === void 0) return subject.name;
20790
+ return formatDeviceName(subject, form, {
20791
+ parent: scope?.parent !== void 0 ? scope.parent : index === null ? null : parentFromIndex(subject, index),
20792
+ siblings: scope?.siblings ?? (index === null ? [] : siblingsFromIndex(subject, index))
20793
+ }, nouns);
20794
+ }
20795
+ function useDeviceLabel(device, form, scope) {
20796
+ return resolveDeviceLabel(device, form, useDeviceNameIndex(), useDeviceNameNouns(), scope);
20797
+ }
20798
+ /** `#id` until the fleet answers, and `#id` when the id is not in it. */
20799
+ function useDeviceLabelById(id, form, pendingName) {
20800
+ const index = useDeviceNameIndex();
20801
+ const nouns = useDeviceNameNouns();
20802
+ if (index === null) return pendingName ?? `#${String(id)}`;
20803
+ const device = index.byId.get(id);
20804
+ if (device === void 0) return `#${String(id)}`;
20805
+ return resolveDeviceLabel(device, form, index, nouns);
20806
+ }
20807
+ //#endregion
20808
+ //#region src/composites/device-name/DeviceName.tsx
20809
+ function DeviceName({ device, form, parent, siblings, className, testId }) {
20810
+ const label = useDeviceLabel(device, form, {
20811
+ ...parent !== void 0 ? { parent } : {},
20812
+ ...siblings !== void 0 ? { siblings } : {}
20813
+ });
20814
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
20815
+ className,
20816
+ title: label,
20817
+ "data-testid": testId,
20818
+ children: label
20819
+ });
20820
+ }
20821
+ //#endregion
19630
20822
  //#region src/hooks/use-device-proxy.ts
19631
20823
  /**
19632
20824
  * useDeviceProxy — canonical React entry point for a device.
@@ -20529,6 +21721,9 @@ function GridCellBox({ cell, selected, onSelect, onDragStart }) {
20529
21721
  }, handle))
20530
21722
  });
20531
21723
  }
21724
+ function CameraOptionLabel({ camera }) {
21725
+ return useDeviceLabel(camera, "compact");
21726
+ }
20532
21727
  function CellInspector({ cell, index, cameras, onChange, onRemove }) {
20533
21728
  const t = useUiText();
20534
21729
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -20559,7 +21754,7 @@ function CellInspector({ cell, index, cameras, onChange, onRemove }) {
20559
21754
  className: "flex-1 rounded border border-border bg-transparent px-1 py-0.5",
20560
21755
  children: cameras.map((camera) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
20561
21756
  value: String(camera.id),
20562
- children: camera.name
21757
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CameraOptionLabel, { camera })
20563
21758
  }, camera.id))
20564
21759
  })]
20565
21760
  }),
@@ -48247,17 +49442,19 @@ function SensorHeroInner({ trpc, device }) {
48247
49442
  if (resolution.kind === "quiet") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
48248
49443
  className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-border bg-surface p-4 min-h-[96px]",
48249
49444
  "data-testid": "sensor-hero-quiet",
48250
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48251
- className: "text-sm text-foreground-subtle",
48252
- children: device.name
49445
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49446
+ device,
49447
+ form: "full",
49448
+ className: "text-sm text-foreground-subtle"
48253
49449
  })
48254
49450
  });
48255
49451
  const slice = resolution.kind === "value" ? resolution.slice : void 0;
48256
49452
  if (descriptor === void 0 || slice === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
48257
49453
  className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-border bg-surface p-4 min-h-[96px]",
48258
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48259
- className: "text-sm text-foreground-subtle",
48260
- children: device.name
49454
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49455
+ device,
49456
+ form: "full",
49457
+ className: "text-sm text-foreground-subtle"
48261
49458
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48262
49459
  className: "text-xs text-foreground-subtle italic",
48263
49460
  children: t("uiLibrary.sensorHeroCard.awaiting", "Awaiting state…")
@@ -48297,9 +49494,10 @@ function SensorHeroInner({ trpc, device }) {
48297
49494
  "data-testid": "sensor-hero-occupancy",
48298
49495
  children: occupancyOn ? t("uiLibrary.sensorHeroCard.occupied", "Occupied") : t("uiLibrary.sensorHeroCard.clear", "Clear")
48299
49496
  }),
48300
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48301
- className: "text-sm text-foreground-subtle",
48302
- children: device.name
49497
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49498
+ device,
49499
+ form: "full",
49500
+ className: "text-sm text-foreground-subtle"
48303
49501
  })
48304
49502
  ]
48305
49503
  });
@@ -48314,9 +49512,10 @@ function SensorHeroInner({ trpc, device }) {
48314
49512
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48315
49513
  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"),
48316
49514
  children: label
48317
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48318
- className: "text-sm text-foreground-subtle",
48319
- children: device.name
49515
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49516
+ device,
49517
+ form: "full",
49518
+ className: "text-sm text-foreground-subtle"
48320
49519
  })]
48321
49520
  });
48322
49521
  }
@@ -48324,9 +49523,10 @@ function SensorHeroInner({ trpc, device }) {
48324
49523
  if (!isStringSliceValue(slice)) return null;
48325
49524
  if (slice.value === "") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
48326
49525
  className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-border bg-surface p-4 min-h-[96px]",
48327
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48328
- className: "text-sm text-foreground-subtle",
48329
- children: device.name
49526
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49527
+ device,
49528
+ form: "full",
49529
+ className: "text-sm text-foreground-subtle"
48330
49530
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48331
49531
  className: "text-xs text-foreground-subtle italic",
48332
49532
  children: t("uiLibrary.sensorHeroCard.awaiting", "Awaiting state…")
@@ -48338,9 +49538,10 @@ function SensorHeroInner({ trpc, device }) {
48338
49538
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48339
49539
  className: "text-3xl font-semibold tabular-nums text-foreground",
48340
49540
  children: formatControlDateTime(slice.value, format)
48341
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48342
- className: "text-sm text-foreground-subtle",
48343
- children: device.name
49541
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49542
+ device,
49543
+ form: "full",
49544
+ className: "text-sm text-foreground-subtle"
48344
49545
  })]
48345
49546
  });
48346
49547
  }
@@ -48352,9 +49553,10 @@ function SensorHeroInner({ trpc, device }) {
48352
49553
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48353
49554
  className: "text-xl font-semibold text-foreground",
48354
49555
  children: slice.value
48355
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48356
- className: "text-sm text-foreground-subtle",
48357
- children: device.name
49556
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49557
+ device,
49558
+ form: "full",
49559
+ className: "text-sm text-foreground-subtle"
48358
49560
  })]
48359
49561
  });
48360
49562
  }
@@ -48365,9 +49567,10 @@ function SensorHeroInner({ trpc, device }) {
48365
49567
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48366
49568
  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"),
48367
49569
  children: label
48368
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48369
- className: "text-sm text-foreground-subtle",
48370
- children: device.name
49570
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49571
+ device,
49572
+ form: "full",
49573
+ className: "text-sm text-foreground-subtle"
48371
49574
  })]
48372
49575
  });
48373
49576
  }
@@ -55122,7 +56325,9 @@ var CONTROL_FILLS = {
55122
56325
  [_camstack_types.DeviceType.Container]: false,
55123
56326
  [_camstack_types.DeviceType.Image]: false,
55124
56327
  [_camstack_types.DeviceType.PetFeeder]: false,
55125
- [_camstack_types.DeviceType.Dishwasher]: false
56328
+ [_camstack_types.DeviceType.Dishwasher]: false,
56329
+ [_camstack_types.DeviceType.Washer]: false,
56330
+ [_camstack_types.DeviceType.Dryer]: false
55126
56331
  };
55127
56332
  var DEVICE_TYPE_VALUES = new Set(Object.values(_camstack_types.DeviceType));
55128
56333
  /**
@@ -56378,6 +57583,7 @@ function useContainerChildren(deviceId) {
56378
57583
  var SENSOR_VALUE_TYPES = new Set([_camstack_types.DeviceType.Sensor, _camstack_types.DeviceType.Presence]);
56379
57584
  function DeviceItemActions({ trpc, device, controlPlacement }) {
56380
57585
  const containerChildren = useContainerChildren(device.id);
57586
+ const deviceLabel = useDeviceLabel(device, "compact");
56381
57587
  const placement = controlPlacement ?? "right";
56382
57588
  if (!!device.disabled) return null;
56383
57589
  if (device.type === _camstack_types.DeviceType.Container) {
@@ -56433,7 +57639,7 @@ function DeviceItemActions({ trpc, device, controlPlacement }) {
56433
57639
  showReboot && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RebootQuickAction, {
56434
57640
  trpc,
56435
57641
  deviceId: device.id,
56436
- deviceName: device.name
57642
+ deviceName: deviceLabel
56437
57643
  }),
56438
57644
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdminLinkQuickAction, {
56439
57645
  trpc,
@@ -56669,9 +57875,16 @@ function ChildRow$1({ trpc, child, onNavigate, parentName }) {
56669
57875
  running: runningSlice === void 0 || runningSlice.running === null ? null : runningSlice.running,
56670
57876
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChildIcon, { className: "h-3 w-3 text-foreground-subtle flex-shrink-0" })
56671
57877
  }),
56672
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
56673
- className: "text-[10.5px] text-foreground flex-1 truncate",
56674
- children: childListName(child, parentName)
57878
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
57879
+ device: child,
57880
+ form: "nested",
57881
+ parent: {
57882
+ id: child.parentDeviceId ?? 0,
57883
+ name: parentName,
57884
+ type: "unknown",
57885
+ stableId: ""
57886
+ },
57887
+ className: "text-[10.5px] text-foreground flex-1 truncate"
56675
57888
  }),
56676
57889
  showMotionTrigger && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChildMotionTriggerToggle, {
56677
57890
  trpc,
@@ -56778,8 +57991,9 @@ function IconAction({ icon: Icon, label, onClick, danger = false }) {
56778
57991
  function DeviceDeleteAction({ device, onDelete }) {
56779
57992
  const t = useUiText();
56780
57993
  const confirm = useConfirm();
57994
+ const deviceLabel = useDeviceLabel(device, "full");
56781
57995
  const requestDelete = async () => {
56782
- const label = device.name.trim() !== "" ? device.name : `#${String(device.id)}`;
57996
+ const label = deviceLabel.trim() !== "" ? deviceLabel : `#${String(device.id)}`;
56783
57997
  if (await confirm({
56784
57998
  title: t("uiLibrary.deviceDeleteAction.title", "Delete device?"),
56785
57999
  message: t("uiLibrary.deviceDeleteAction.message", "This will remove \"{{label}}\" and all its settings. This action cannot be undone.", { label }),
@@ -57062,7 +58276,7 @@ var CHEVRON_SLOT_CLASS = {
57062
58276
  horizontal: "h-3.5 w-3.5",
57063
58277
  stacked: "h-4 w-4 mt-0.5"
57064
58278
  };
57065
- function DeviceItemHeader({ trpc, device, variant, flags, parent, integrationIcon, hasChildren, expanded, onToggleExpand, onNavigateToParent, stacked = false, nameOverflow = "truncate" }) {
58279
+ function DeviceItemHeader({ trpc, device, variant, flags, parent, integrationIcon, hasChildren, expanded, onToggleExpand, onNavigateToParent, stacked = false, nameOverflow = "truncate", nameForm = "compact" }) {
57066
58280
  const t = useUiText();
57067
58281
  const containerChildren = useContainerChildren(device.id);
57068
58282
  const roleDefaults = useRoleDisplayDefaults();
@@ -57073,6 +58287,13 @@ function DeviceItemHeader({ trpc, device, variant, flags, parent, integrationIco
57073
58287
  const slotClass = stacked ? CHEVRON_SLOT_CLASS.stacked : CHEVRON_SLOT_CLASS.horizontal;
57074
58288
  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");
57075
58289
  const lineClass = nameOverflow === "scroll" ? NAME_TEXT_CLASS : "truncate";
58290
+ const nameParent = nameForm === "nested" && parent !== null ? {
58291
+ id: parent.id,
58292
+ name: parent.name,
58293
+ type: parent.type ?? "unknown",
58294
+ stableId: ""
58295
+ } : void 0;
58296
+ const parentLabel = useDeviceLabelById(parent?.id ?? 0, "compact", parent?.name);
57076
58297
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
57077
58298
  className: cn(stacked ? "flex items-start gap-2 w-full min-w-0" : "flex items-center gap-2 flex-1 min-w-0"),
57078
58299
  children: [
@@ -57103,11 +58324,12 @@ function DeviceItemHeader({ trpc, device, variant, flags, parent, integrationIco
57103
58324
  children: [flags.showIntegrationIcon && integrationIcon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
57104
58325
  className: "flex-shrink-0",
57105
58326
  children: integrationIcon
57106
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
57107
- "data-testid": "device-name",
57108
- 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]")),
57109
- title: device.name,
57110
- children: device.name
58327
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
58328
+ device,
58329
+ form: nameForm,
58330
+ ...nameParent !== void 0 ? { parent: nameParent } : {},
58331
+ testId: "device-name",
58332
+ 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]"))
57111
58333
  })]
57112
58334
  }),
57113
58335
  (flags.showId || flags.showStableId) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -57136,12 +58358,12 @@ function DeviceItemHeader({ trpc, device, variant, flags, parent, integrationIco
57136
58358
  },
57137
58359
  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"),
57138
58360
  title: t("uiLibrary.deviceItemHeader.openParent", "Open parent: {{name}} (#{{id}})", {
57139
- name: parent.name,
58361
+ name: parentLabel,
57140
58362
  id: parent.id
57141
58363
  }),
57142
58364
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Network, { className: "h-2.5 w-2.5 shrink-0" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
57143
58365
  className: lineClass,
57144
- children: t("uiLibrary.deviceItemHeader.under", "Under: {{name}}", { name: parent.name })
58366
+ children: t("uiLibrary.deviceItemHeader.under", "Under: {{name}}", { name: parentLabel })
57145
58367
  })]
57146
58368
  })
57147
58369
  ]
@@ -57270,7 +58492,9 @@ function HoverZoomImage({ src, alt, caption, placeholder = "No image", thumbClas
57270
58492
  * the 288 px hover zoom. */
57271
58493
  var PREVIEW_SNAPSHOT_WIDTH = 480;
57272
58494
  function DeviceItemPreview({ trpc, device, status, enabled, showStatusPills = false }) {
57273
- const snapshot = useDeviceSnapshotImage(enabled && device.online ? device.id : null, { width: PREVIEW_SNAPSHOT_WIDTH });
58495
+ const liveActive = enabled && device.online;
58496
+ const deviceLabel = useDeviceLabel(device, "compact");
58497
+ const snapshot = useDeviceSnapshotImage(liveActive ? device.id : null, { width: PREVIEW_SNAPSHOT_WIDTH });
57274
58498
  const isBatteryOperated = (device.features ?? []).includes("battery-operated");
57275
58499
  const wantPills = showStatusPills && isBatteryOperated && !device.disabled;
57276
58500
  const batteryStatus = useDeviceBattery(trpc, wantPills ? device.id : null);
@@ -57286,7 +58510,7 @@ function DeviceItemPreview({ trpc, device, status, enabled, showStatusPills = fa
57286
58510
  wantLinkPill && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NetworkLinkBadge, { status: linkStatus }),
57287
58511
  enabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HoverZoomImage, {
57288
58512
  src: offline ? null : snapshot.src,
57289
- alt: device.name,
58513
+ alt: deviceLabel,
57290
58514
  placeholder,
57291
58515
  thumbClassName: "h-7 w-7 rounded"
57292
58516
  })
@@ -57509,6 +58733,14 @@ function DeviceItem(props) {
57509
58733
  const flags = resolveFlags(props);
57510
58734
  const variant = props.variant ?? "compact";
57511
58735
  const view = props.view ?? "card";
58736
+ const nameForm = props.isAccessoryRow ? "nested" : "compact";
58737
+ const nameParent = nameForm === "nested" && parent ? {
58738
+ id: parent.id,
58739
+ name: parent.name,
58740
+ type: parent.type ?? "unknown",
58741
+ stableId: ""
58742
+ } : void 0;
58743
+ const rowLabel = useDeviceLabel(device, nameForm, nameParent !== void 0 ? { parent: nameParent } : void 0);
57512
58744
  const enabled = !device.disabled;
57513
58745
  const isCamera = device.isCamera ?? device.type.toLowerCase() === "camera";
57514
58746
  const childrenList = props.children ?? [];
@@ -57584,7 +58816,7 @@ function DeviceItem(props) {
57584
58816
  checked: selection.selected,
57585
58817
  onChange: selection.onToggle,
57586
58818
  onClick: (e) => e.stopPropagation(),
57587
- "aria-label": t("uiLibrary.deviceItem.select", "Select {{name}}", { name: device.name }),
58819
+ "aria-label": t("uiLibrary.deviceItem.select", "Select {{name}}", { name: rowLabel }),
57588
58820
  className: "flex-shrink-0"
57589
58821
  }),
57590
58822
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceItemHeader, {
@@ -57593,6 +58825,7 @@ function DeviceItem(props) {
57593
58825
  variant,
57594
58826
  flags,
57595
58827
  parent: parent ?? null,
58828
+ nameForm,
57596
58829
  integrationIcon: integrationIcon ?? null,
57597
58830
  hasChildren,
57598
58831
  expanded,
@@ -57661,7 +58894,7 @@ function DeviceItem(props) {
57661
58894
  checked: selection.selected,
57662
58895
  onChange: selection.onToggle,
57663
58896
  onClick: (e) => e.stopPropagation(),
57664
- "aria-label": t("uiLibrary.deviceItem.select", "Select {{name}}", { name: device.name }),
58897
+ "aria-label": t("uiLibrary.deviceItem.select", "Select {{name}}", { name: rowLabel }),
57665
58898
  className: "flex-shrink-0 mt-0.5"
57666
58899
  }),
57667
58900
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceItemHeader, {
@@ -57670,6 +58903,7 @@ function DeviceItem(props) {
57670
58903
  variant,
57671
58904
  flags,
57672
58905
  parent: parent ?? null,
58906
+ nameForm,
57673
58907
  integrationIcon: integrationIcon ?? null,
57674
58908
  hasChildren,
57675
58909
  expanded,
@@ -57730,6 +58964,14 @@ function DeviceItemTableRow(props) {
57730
58964
  const t = useUiText();
57731
58965
  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;
57732
58966
  const containerChildren = useContainerChildren(device.id);
58967
+ const nameForm = isAccessoryRow ? "nested" : "compact";
58968
+ const nameParent = nameForm === "nested" && parent ? {
58969
+ id: parent.id,
58970
+ name: parent.name,
58971
+ type: parent.type ?? "unknown",
58972
+ stableId: ""
58973
+ } : void 0;
58974
+ const rowLabel = useDeviceLabel(device, nameForm, nameParent !== void 0 ? { parent: nameParent } : void 0);
57733
58975
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
57734
58976
  onClick: onRowClick,
57735
58977
  ...rowSelects ? {
@@ -57754,7 +58996,7 @@ function DeviceItemTableRow(props) {
57754
58996
  checked: selection.selected,
57755
58997
  onChange: selection.onToggle,
57756
58998
  onClick: (e) => e.stopPropagation(),
57757
- "aria-label": t("uiLibrary.deviceItem.select", "Select {{name}}", { name: device.name }),
58999
+ "aria-label": t("uiLibrary.deviceItem.select", "Select {{name}}", { name: rowLabel }),
57758
59000
  className: "flex-shrink-0"
57759
59001
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceItemHeader, {
57760
59002
  trpc,
@@ -57762,6 +59004,7 @@ function DeviceItemTableRow(props) {
57762
59004
  variant,
57763
59005
  flags,
57764
59006
  parent,
59007
+ nameForm,
57765
59008
  integrationIcon,
57766
59009
  hasChildren,
57767
59010
  expanded,
@@ -58500,13 +59743,13 @@ function TableRowGroup({ parent, accessories, hasAccessories, expanded, onToggle
58500
59743
  function AccessorySubRow({ trpc, accessory, parentName, showType, showManufacturer, showFeatures, onNavigate, muted }) {
58501
59744
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceItem, {
58502
59745
  trpc,
58503
- device: {
58504
- ...accessory,
58505
- name: childListName(accessory, parentName)
58506
- },
59746
+ device: accessory,
58507
59747
  view: "table",
58508
59748
  integrationIcon: null,
58509
- parent: null,
59749
+ parent: accessory.parentDeviceId === null ? null : {
59750
+ id: accessory.parentDeviceId,
59751
+ name: parentName
59752
+ },
58510
59753
  isAccessoryRow: true,
58511
59754
  showChildrenAccordion: false,
58512
59755
  onNavigate: onNavigate ?? void 0,
@@ -59305,6 +60548,15 @@ function DeviceExportSetup({ addonId }) {
59305
60548
  }), status.setup && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SetupSection, { setup: status.setup })]
59306
60549
  });
59307
60550
  }
60551
+ function ExportSelectCell({ device, checked, onToggle }) {
60552
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
60553
+ type: "checkbox",
60554
+ checked,
60555
+ onChange: onToggle,
60556
+ onClick: (e) => e.stopPropagation(),
60557
+ "aria-label": useUiText()("uiLibrary.deviceExport.select", "Select {{name}}", { name: useDeviceLabel(device, "compact") })
60558
+ });
60559
+ }
59308
60560
  /**
59309
60561
  * Per-row Expose/Exposed toggle. Reflects whether the row's device is exposed
59310
60562
  * to THIS exporter and flips it through the SAME runner the bulk buttons use —
@@ -59316,6 +60568,7 @@ function DeviceExportSetup({ addonId }) {
59316
60568
  */
59317
60569
  function ExportRowToggle({ addonId, device, exposed, busy, onToggle }) {
59318
60570
  const t = useUiText();
60571
+ const deviceLabel = useDeviceLabel(device, "compact");
59319
60572
  const handleClick = (e) => {
59320
60573
  e.stopPropagation();
59321
60574
  if (busy) return;
@@ -59326,7 +60579,7 @@ function ExportRowToggle({ addonId, device, exposed, busy, onToggle }) {
59326
60579
  variant: exposed ? "ghost" : "primary",
59327
60580
  disabled: busy,
59328
60581
  onClick: handleClick,
59329
- "aria-label": exposed ? t("uiLibrary.deviceExport.unexposeDevice", "Unexpose {{name}}", { name: device.name }) : t("uiLibrary.deviceExport.exposeDevice", "Expose {{name}}", { name: device.name }),
60582
+ "aria-label": exposed ? t("uiLibrary.deviceExport.unexposeDevice", "Unexpose {{name}}", { name: deviceLabel }) : t("uiLibrary.deviceExport.exposeDevice", "Expose {{name}}", { name: deviceLabel }),
59330
60583
  title: exposed ? t("uiLibrary.deviceExport.stopTitle", "Stop exposing to \"{{addon}}\"", { addon: addonId }) : t("uiLibrary.deviceExport.exposeTitle", "Expose to \"{{addon}}\"", { addon: addonId }),
59331
60584
  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")] })
59332
60585
  });
@@ -59542,12 +60795,10 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
59542
60795
  key: "select",
59543
60796
  header: "",
59544
60797
  headerClassName: "w-8",
59545
- cell: (device) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
59546
- type: "checkbox",
60798
+ cell: (device) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExportSelectCell, {
60799
+ device,
59547
60800
  checked: selectedIds.has(String(device.id)),
59548
- onChange: () => toggleSelection(String(device.id)),
59549
- onClick: (e) => e.stopPropagation(),
59550
- "aria-label": t("uiLibrary.deviceExport.select", "Select {{name}}", { name: device.name })
60801
+ onToggle: () => toggleSelection(String(device.id))
59551
60802
  })
59552
60803
  },
59553
60804
  {
@@ -59566,9 +60817,10 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
59566
60817
  {
59567
60818
  key: "name",
59568
60819
  header: t("uiLibrary.deviceExport.colName", "Name"),
59569
- cell: (device) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
59570
- className: "text-xs font-medium text-foreground",
59571
- children: device.name
60820
+ cell: (device) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
60821
+ device,
60822
+ form: "compact",
60823
+ className: "text-xs font-medium text-foreground"
59572
60824
  })
59573
60825
  },
59574
60826
  {
@@ -66381,6 +67633,26 @@ function deviceGroupTitle(group, t = defaultUiText) {
66381
67633
  function isDeviceNoun(noun) {
66382
67634
  return noun === "camera" || noun === "device";
66383
67635
  }
67636
+ /** Compact label for a device id. String ids (nodes, packages) keep the stored text. */
67637
+ function usePickerDeviceLabel(id, stored) {
67638
+ const lookedUp = useDeviceLabelById(typeof id === "number" ? id : 0, "compact", stored);
67639
+ return typeof id === "number" ? lookedUp : stored;
67640
+ }
67641
+ function PickerDeviceName({ id, stored, className }) {
67642
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
67643
+ className,
67644
+ children: usePickerDeviceLabel(id, stored)
67645
+ });
67646
+ }
67647
+ function PickerChip({ id, stored, removed, onRemove }) {
67648
+ const label = usePickerDeviceLabel(id, stored);
67649
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectedChip, {
67650
+ testId: `picker-chip-${String(id)}`,
67651
+ label,
67652
+ warning: removed,
67653
+ ...onRemove !== void 0 ? { onRemove } : {}
67654
+ });
67655
+ }
66384
67656
  function chipsOf(value, devices, fleet, noun, t) {
66385
67657
  const byId = new Map(devices.map((device) => [device.id, device.name]));
66386
67658
  const orphans = new Map(offListSelections(value, devices, fleet.devices, fleet.state).map((row) => [row.id, row]));
@@ -66416,7 +67688,7 @@ function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, s
66416
67688
  const t = useUiText();
66417
67689
  const groupTitle = groupTitleProp ?? ((group) => deviceGroupTitle(group, t));
66418
67690
  const lockedNote = lockedNoteProp ?? t("uiLibrary.devicePicker.alreadyAdded", "Already added");
66419
- const devices = (0, react$1.useMemo)(() => withResolvedParentDetail(devicesIn), [devicesIn]);
67691
+ const devices = (0, react$1.useMemo)(() => isDeviceNoun(noun) ? devicesIn : withResolvedParentDetail(devicesIn), [devicesIn, noun]);
66420
67692
  const judge = fleet ?? {
66421
67693
  state: listState,
66422
67694
  devices: listState === "answered" ? devices : void 0
@@ -66451,10 +67723,10 @@ function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, s
66451
67723
  chips.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
66452
67724
  className: "text-[11px] text-foreground-subtle",
66453
67725
  children: emptyLabel ?? (emptyMeansAll ? t("uiLibrary.devicePicker.everyNoRestriction", "Every {{noun}} — no restriction", { noun: pickerNounLabel(noun, t) }) : t("uiLibrary.devicePicker.noneSelected", "None selected"))
66454
- }) : overflow.shown.map((chip) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectedChip, {
66455
- testId: `picker-chip-${String(chip.id)}`,
66456
- label: chip.label,
66457
- warning: chip.removed,
67726
+ }) : overflow.shown.map((chip) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PickerChip, {
67727
+ id: chip.id,
67728
+ stored: chip.label,
67729
+ removed: chip.removed,
66458
67730
  ...!disabled ? { onRemove: () => onChange(value.filter((id) => id !== chip.id)) } : {}
66459
67731
  }, chip.id)),
66460
67732
  overflow.hidden > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -66747,9 +68019,10 @@ function PickerRowView({ row, value, onChange, onToggleGroup, annotate, groupTit
66747
68019
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
66748
68020
  className: "flex min-w-0 flex-1 flex-col",
66749
68021
  children: [
66750
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
66751
- className: "truncate",
66752
- children: row.device.name
68022
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PickerDeviceName, {
68023
+ id: row.device.id,
68024
+ stored: row.device.name,
68025
+ className: "truncate"
66753
68026
  }),
66754
68027
  second !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
66755
68028
  className: "truncate text-[10px] text-foreground-subtle",
@@ -69840,10 +71113,14 @@ exports.DEVICE_COLUMNS = DEVICE_COLUMNS;
69840
71113
  exports.DEVICE_EXPORT_PAGE_SIZE_KEY = DEVICE_EXPORT_PAGE_SIZE_KEY;
69841
71114
  exports.DEVICE_LIST_PAGE_SIZE_KEY = DEVICE_LIST_PAGE_SIZE_KEY;
69842
71115
  exports.DEVICE_LIST_PAGE_SIZE_OPTIONS = DEVICE_LIST_PAGE_SIZE_OPTIONS;
71116
+ exports.DEVICE_NAME_FORMS = DEVICE_NAME_FORMS;
71117
+ exports.DEVICE_NAME_JOIN = DEVICE_NAME_JOIN;
69843
71118
  exports.DEVICE_PICKER_GROUP_STORAGE_KEY = DEVICE_PICKER_GROUP_STORAGE_KEY;
69844
71119
  exports.DEVICE_ROLE_META = DEVICE_ROLE_META;
71120
+ exports.DEVICE_ROLE_NOUNS = DEVICE_ROLE_NOUNS;
69845
71121
  exports.DEVICE_TYPE_CONTROL = DEVICE_TYPE_CONTROL;
69846
71122
  exports.DEVICE_TYPE_META = DEVICE_TYPE_META;
71123
+ exports.DEVICE_TYPE_NOUNS = DEVICE_TYPE_NOUNS;
69847
71124
  exports.DataRecordsHub = DataRecordsHub;
69848
71125
  exports.DataTable = DataTable;
69849
71126
  exports.DataTableFilterBar = DataTableFilterBar;
@@ -69860,11 +71137,14 @@ exports.DeviceContextProvider = DeviceContextProvider;
69860
71137
  exports.DeviceExportPanel = DeviceExportPanel;
69861
71138
  exports.DeviceExportSetup = DeviceExportSetup;
69862
71139
  exports.DeviceFilterBar = DeviceFilterBar;
71140
+ exports.DeviceFleetFromServer = DeviceFleetFromServer;
71141
+ exports.DeviceFleetProvider = DeviceFleetProvider;
69863
71142
  exports.DeviceGrid = DeviceGrid;
69864
71143
  exports.DeviceHistoryView = DeviceHistoryView;
69865
71144
  exports.DeviceItem = DeviceItem;
69866
71145
  exports.DeviceList = DeviceList;
69867
71146
  exports.DeviceMultiSelectField = DeviceMultiSelectField;
71147
+ exports.DeviceName = DeviceName;
69868
71148
  exports.DevicePicker = DevicePicker;
69869
71149
  exports.DeviceQuickDetailProvider = DeviceQuickDetailProvider;
69870
71150
  exports.DeviceSelectField = DeviceSelectField;
@@ -70293,6 +71573,7 @@ exports.ViewSwitch = ViewSwitch;
70293
71573
  exports.VodPlaybackProvider = VodPlaybackProvider;
70294
71574
  exports.Volume2 = Volume2;
70295
71575
  exports.VolumeX = VolumeX;
71576
+ exports.WashingMachine = WashingMachine;
70296
71577
  exports.WaterHeaterHeroCard = WaterHeaterHeroCard;
70297
71578
  exports.WaterHeaterInlineControl = WaterHeaterInlineControl;
70298
71579
  exports.Waves = Waves;
@@ -70395,6 +71676,7 @@ exports.describeDeviceSelector = describeDeviceSelector;
70395
71676
  exports.deviceIntegrationResolver = deviceIntegrationResolver;
70396
71677
  exports.deviceListStateOf = deviceListStateOf;
70397
71678
  exports.deviceMatchesFilter = deviceMatchesFilter;
71679
+ exports.deviceNameNouns = deviceNameNouns;
70398
71680
  exports.deviceOptionLabel = deviceOptionLabel;
70399
71681
  exports.deviceRoleMeta = deviceRoleMeta;
70400
71682
  exports.deviceRoleMetaOf = deviceRoleMetaOf;
@@ -70422,10 +71704,12 @@ exports.findTimezone = _camstack_types.findTimezone;
70422
71704
  exports.flattenPickerGrouping = flattenPickerGrouping;
70423
71705
  exports.flattenPickerRows = flattenPickerRows;
70424
71706
  exports.fleetViewOf = fleetViewOf;
71707
+ exports.foldDeviceName = foldDeviceName;
70425
71708
  exports.formatChartBytes = formatChartBytes;
70426
71709
  exports.formatChartNumber = formatChartNumber;
70427
71710
  exports.formatChartValue = formatChartValue;
70428
71711
  exports.formatControlDateTime = formatControlDateTime;
71712
+ exports.formatDeviceName = formatDeviceName;
70429
71713
  exports.formatLastSeen = formatLastSeen;
70430
71714
  exports.formatNumeric = formatNumeric;
70431
71715
  exports.formatSettingValue = formatSettingValue;
@@ -70453,6 +71737,7 @@ exports.humanizeName = humanizeName;
70453
71737
  exports.humidifierTint = humidifierTint;
70454
71738
  exports.iconGlyphsFailed = iconGlyphsFailed;
70455
71739
  exports.iconGlyphsLoaded = iconGlyphsLoaded;
71740
+ exports.indexDeviceNames = indexDeviceNames;
70456
71741
  exports.initialScrubState = initialScrubState;
70457
71742
  exports.insertAtSelection = insertAtSelection;
70458
71743
  exports.isAbsentProvider = isAbsentProvider;
@@ -70460,6 +71745,7 @@ exports.isAbsentProvider$1 = isAbsentProvider;
70460
71745
  exports.isByteUnit = isByteUnit;
70461
71746
  exports.isConsumableLevelField = isConsumableLevelField;
70462
71747
  exports.isDataTableFilterActive = isDataTableFilterActive;
71748
+ exports.isDeviceNameStock = isDeviceNameStock;
70463
71749
  Object.defineProperty(exports, "isDrawableSubjectPath", {
70464
71750
  enumerable: true,
70465
71751
  get: function() {
@@ -70501,6 +71787,7 @@ exports.nodeTextColorClass = nodeTextColorClass;
70501
71787
  exports.normalizeForSearch = normalizeForSearch;
70502
71788
  exports.normalizeLanguage = normalizeLanguage;
70503
71789
  exports.normalizeSelection = normalizeSelection;
71790
+ exports.nounSpecFor = nounSpecFor;
70504
71791
  exports.occupancyFacets = occupancyFacets;
70505
71792
  exports.occupancyLines = occupancyLines;
70506
71793
  exports.offListSelections = offListSelections;
@@ -70555,6 +71842,7 @@ exports.resolveContainerPrimary = resolveContainerPrimary;
70555
71842
  exports.resolveControlAlign = resolveControlAlign;
70556
71843
  exports.resolveDeviceControl = resolveDeviceControl;
70557
71844
  exports.resolveDeviceIntegration = resolveDeviceIntegration;
71845
+ exports.resolveDeviceLabel = resolveDeviceLabel;
70558
71846
  exports.resolveDisplayIcon = resolveDisplayIcon;
70559
71847
  exports.resolveEffectiveDefaultModel = resolveEffectiveDefaultModel;
70560
71848
  exports.resolveEffectiveStepModel = resolveEffectiveStepModel;
@@ -70573,6 +71861,7 @@ exports.rootSourceState = rootSourceState;
70573
71861
  exports.routerBasename = routerBasename;
70574
71862
  exports.runningDurationSince = runningDurationSince;
70575
71863
  exports.runningStatusParts = runningStatusParts;
71864
+ exports.sameDeviceName = sameDeviceName;
70576
71865
  exports.sameLocationSuggestions = sameLocationSuggestions;
70577
71866
  exports.scrubReducer = scrubReducer;
70578
71867
  exports.searchSteps = searchSteps;
@@ -70606,6 +71895,7 @@ exports.stepFieldDomId = stepFieldDomId;
70606
71895
  exports.stepHasModelForFormat = stepHasModelForFormat;
70607
71896
  exports.stepLocalFields = stepLocalFields;
70608
71897
  exports.stepModelOptions = stepModelOptions;
71898
+ exports.stripDeviceParentPrefix = stripDeviceParentPrefix;
70609
71899
  exports.stripParentNamePrefix = stripParentNamePrefix;
70610
71900
  Object.defineProperty(exports, "subjectPathSampleFromBox", {
70611
71901
  enumerable: true,
@@ -70903,6 +72193,8 @@ exports.useDeviceHistoryRecord = useDeviceHistoryRecord;
70903
72193
  exports.useDeviceHistoryRecordBuckets = useDeviceHistoryRecordBuckets;
70904
72194
  exports.useDeviceHistorySetDeviceEnabled = useDeviceHistorySetDeviceEnabled;
70905
72195
  exports.useDeviceId = useDeviceId;
72196
+ exports.useDeviceLabel = useDeviceLabel;
72197
+ exports.useDeviceLabelById = useDeviceLabelById;
70906
72198
  exports.useDeviceListPageSize = useDeviceListPageSize;
70907
72199
  exports.useDeviceManagerAddLocation = useDeviceManagerAddLocation;
70908
72200
  exports.useDeviceManagerAdoptDevice = useDeviceManagerAdoptDevice;
@@ -70996,6 +72288,8 @@ exports.useDeviceManagerTestField = useDeviceManagerTestField;
70996
72288
  exports.useDeviceManagerUpdateConfig = useDeviceManagerUpdateConfig;
70997
72289
  exports.useDeviceManagerUpdateDeviceField = useDeviceManagerUpdateDeviceField;
70998
72290
  exports.useDeviceManagerUpdateDeviceFieldsBatch = useDeviceManagerUpdateDeviceFieldsBatch;
72291
+ exports.useDeviceNameIndex = useDeviceNameIndex;
72292
+ exports.useDeviceNameNouns = useDeviceNameNouns;
70999
72293
  exports.useDeviceNetworkLink = useDeviceNetworkLink;
71000
72294
  exports.useDeviceOpsGetConfigEntries = useDeviceOpsGetConfigEntries;
71001
72295
  exports.useDeviceOpsGetRawState = useDeviceOpsGetRawState;
@@ -71213,6 +72507,7 @@ exports.useMeshNetworkListPeers = useMeshNetworkListPeers;
71213
72507
  exports.useMeshNetworkLogout = useMeshNetworkLogout;
71214
72508
  exports.useMeshNetworkStartLogin = useMeshNetworkStartLogin;
71215
72509
  exports.useMeshNetworkTestConnection = useMeshNetworkTestConnection;
72510
+ exports.useMeterGetStatus = useMeterGetStatus;
71216
72511
  exports.useMetricsProviderCollectSnapshot = useMetricsProviderCollectSnapshot;
71217
72512
  exports.useMetricsProviderGetAddonStats = useMetricsProviderGetAddonStats;
71218
72513
  exports.useMetricsProviderGetCached = useMetricsProviderGetCached;