camstack 1.2.157 → 1.2.159

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.
@@ -23633,9 +23633,9 @@ var require_zod = __commonJS({
23633
23633
  }
23634
23634
  });
23635
23635
 
23636
- // ../system/dist/dist-BETx_5mH.js
23637
- var require_dist_BETx_5mH = __commonJS({
23638
- "../system/dist/dist-BETx_5mH.js"(exports) {
23636
+ // ../system/dist/dist-Cz6h6Nc3.js
23637
+ var require_dist_Cz6h6Nc3 = __commonJS({
23638
+ "../system/dist/dist-Cz6h6Nc3.js"(exports) {
23639
23639
  "use strict";
23640
23640
  var zod = require_zod();
23641
23641
  var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
@@ -31763,6 +31763,51 @@ var require_dist_BETx_5mH = __commonJS({
31763
31763
  unexposeDevice: method(UnexposeInputSchema, zod.z.void(), { kind: "mutation" })
31764
31764
  }
31765
31765
  };
31766
+ var LocationIconIdSchema = zod.z.enum([
31767
+ "map-pin",
31768
+ "house",
31769
+ "door-open",
31770
+ "sofa",
31771
+ "utensils",
31772
+ "bed-double",
31773
+ "bath",
31774
+ "briefcase",
31775
+ "car",
31776
+ "circle-parking",
31777
+ "route",
31778
+ "trees",
31779
+ "umbrella",
31780
+ "waves-ladder",
31781
+ "fence",
31782
+ "package",
31783
+ "warehouse",
31784
+ "store",
31785
+ "sun",
31786
+ "radar"
31787
+ ]);
31788
+ var LocationIconAssignmentSchema = zod.z.object({
31789
+ locationKey: zod.z.string(),
31790
+ icon: LocationIconIdSchema
31791
+ });
31792
+ function locationIconKey(label) {
31793
+ return label.trim().toLowerCase();
31794
+ }
31795
+ var LocationDescriptionAssignmentSchema = zod.z.object({
31796
+ locationKey: zod.z.string(),
31797
+ description: zod.z.string()
31798
+ });
31799
+ var SiteZoneSchema = zod.z.object({
31800
+ zoneKey: zod.z.string(),
31801
+ label: zod.z.string(),
31802
+ icon: LocationIconIdSchema.nullable(),
31803
+ /** Folded location keys, sorted. A location listed here need not be
31804
+ * REGISTERED — devices carry labels nobody registered, and those rooms are
31805
+ * the ones that most want grouping. */
31806
+ locationKeys: zod.z.array(zod.z.string())
31807
+ });
31808
+ function siteZoneKey(label) {
31809
+ return label.trim().toLowerCase();
31810
+ }
31766
31811
  var ProviderStatusSchema = zod.z.object({
31767
31812
  connected: zod.z.boolean(),
31768
31813
  deviceCount: zod.z.number(),
@@ -32412,7 +32457,11 @@ var require_dist_BETx_5mH = __commonJS({
32412
32457
  * reference this label keep their `meta.location` value — the
32413
32458
  * registry is purely a suggestion list. To purge from devices,
32414
32459
  * iterate them and call `setLocation(deviceId, null)`
32415
- * separately (or pass `cascade: true` to do it in one shot). */
32460
+ * separately (or pass `cascade: true` to do it in one shot).
32461
+ *
32462
+ * The room's ICON goes with it, always — a key left behind survives in a
32463
+ * map nobody lists, and a room later re-created under the same name would
32464
+ * silently inherit an icon the operator never chose for it. */
32416
32465
  removeLocation: method(zod.z.object({
32417
32466
  name: zod.z.string(),
32418
32467
  cascade: zod.z.boolean().optional()
@@ -32433,7 +32482,13 @@ var require_dist_BETx_5mH = __commonJS({
32433
32482
  * a suggestion list, and the devices that most need a rename are exactly
32434
32483
  * the ones carrying a label nobody registered. `to` renames onto an
32435
32484
  * existing room by MERGING into it, leaving no duplicate label. An empty
32436
- * `to` throws before anything is written. */
32485
+ * `to` throws before anything is written.
32486
+ *
32487
+ * The room's ICON travels with the rename, including when `from` was
32488
+ * never registered — otherwise it orphans on a key no location carries
32489
+ * any more. A merge keeps the TARGET's icon when it has one: renaming
32490
+ * "Sala" into "Salone" moves devices into Salone, it does not restyle
32491
+ * it. */
32437
32492
  renameLocation: method(zod.z.object({
32438
32493
  from: zod.z.string(),
32439
32494
  to: zod.z.string()
@@ -32441,6 +32496,127 @@ var require_dist_BETx_5mH = __commonJS({
32441
32496
  kind: "mutation",
32442
32497
  auth: "admin"
32443
32498
  }),
32499
+ /** Every location that carries an ICON, as `(locationKey, icon)` pairs.
32500
+ *
32501
+ * ADDITIVE, deliberately: `listLocations` keeps returning `string[]` —
32502
+ * every consumer of it (the Device Info autocomplete, the HA adoption
32503
+ * import, the recorder) reads a list of labels and none of them wants a
32504
+ * shape change. A surface that wants icons asks for them and joins.
32505
+ *
32506
+ * Only assignments that EXIST are returned — a location with no icon is
32507
+ * absent, not `map-pin`. The fallback belongs to the renderer
32508
+ * (`resolveLocationIcon`), not to the store, so "the operator chose the
32509
+ * pin" and "the operator chose nothing" stay distinguishable.
32510
+ *
32511
+ * The join key is `locationIconKey(label)` (trimmed, lower-cased) — the
32512
+ * same equality the registry uses everywhere. Never the operator's
32513
+ * casing: a case-only rename is a real edit here, and a map keyed on the
32514
+ * displayed spelling would orphan its entry on exactly that edit. */
32515
+ listLocationIcons: method(zod.z.void(), zod.z.array(LocationIconAssignmentSchema)),
32516
+ /** Set (or clear) the icon of a location label. `icon: null` removes the
32517
+ * assignment. The label need NOT be registered — the registry is a
32518
+ * suggestion list and devices carry labels nobody registered — but an
32519
+ * empty / whitespace-only label throws. Idempotent.
32520
+ *
32521
+ * The icon vocabulary is CLOSED (`LOCATION_ICON_IDS`) because the admin UI
32522
+ * and the viewer draw from different icon modules; see
32523
+ * `device/location-icon.ts` for the list and for what a surface that
32524
+ * cannot draw a member must do (fall back to `map-pin`, never a hole). */
32525
+ setLocationIcon: method(zod.z.object({
32526
+ location: zod.z.string(),
32527
+ icon: LocationIconIdSchema.nullable()
32528
+ }), zod.z.void(), {
32529
+ kind: "mutation",
32530
+ auth: "admin"
32531
+ }),
32532
+ /** Every location that carries a DESCRIPTION — what the place LOOKS like,
32533
+ * in plain words, given to the vision model alongside the pictures so it
32534
+ * knows what it is looking at. A driveway of cobbles between a brick wall
32535
+ * and three parking spaces reads very differently from a back garden.
32536
+ *
32537
+ * Same additive shape and same join key as `listLocationIcons`, and that
32538
+ * is the point of it being here at all: this lived in the post-analysis
32539
+ * addon's own settings, keyed by a hand-typed label matched exactly and
32540
+ * case-sensitively, while the icon hung off the location itself. A rename
32541
+ * carried the icon and silently orphaned the description. One location,
32542
+ * one authority, one key.
32543
+ *
32544
+ * Absent means nobody wrote one — the digest then tells the model only the
32545
+ * location NAME, exactly as it did before descriptions existed. */
32546
+ listLocationDescriptions: method(zod.z.void(), zod.z.array(LocationDescriptionAssignmentSchema)),
32547
+ /** Set (or clear) a location's description. `description: null`, or a
32548
+ * blank string, removes it — an empty description and no description are
32549
+ * the same thing to every reader, and storing the first would send an
32550
+ * empty line to the model.
32551
+ *
32552
+ * Like `setLocationIcon`, the label need NOT be registered, and an
32553
+ * empty / whitespace-only LABEL throws. */
32554
+ setLocationDescription: method(zod.z.object({
32555
+ location: zod.z.string(),
32556
+ description: zod.z.string().max(400).nullable()
32557
+ }), zod.z.void(), {
32558
+ kind: "mutation",
32559
+ auth: "admin"
32560
+ }),
32561
+ /** Every SITE ZONE — a named part of the property that groups locations
32562
+ * ("Zona notte" holds the bedrooms). One call answers the whole picture:
32563
+ * key, the label as typed, the icon, and the locations inside.
32564
+ *
32565
+ * Together, because a surface that asked for membership separately could
32566
+ * draw a zone whose rooms it has not fetched — and the strip that renders
32567
+ * these puts zones BEFORE locations, so a half-known zone is the first
32568
+ * thing an operator sees.
32569
+ *
32570
+ * Nothing here touches a device: a camera's zone is derived from its
32571
+ * location, never stored on the row. Grouping rooms must not rewrite the
32572
+ * fleet. */
32573
+ listSiteZones: method(zod.z.void(), zod.z.array(SiteZoneSchema)),
32574
+ /** Create a zone, or set the icon of one that exists. `icon: null` clears
32575
+ * the icon and KEEPS the zone — a zone with no icon is a normal zone, and
32576
+ * conflating "no icon" with "no zone" would delete membership on a
32577
+ * mis-click.
32578
+ *
32579
+ * Idempotent, and the label is stored as typed: the key is folded, so
32580
+ * re-running this with different casing re-labels rather than duplicating. */
32581
+ setSiteZone: method(zod.z.object({
32582
+ name: zod.z.string(),
32583
+ icon: LocationIconIdSchema.nullable().optional()
32584
+ }), zod.z.void(), {
32585
+ kind: "mutation",
32586
+ auth: "admin"
32587
+ }),
32588
+ /** Delete a zone. Its LOCATIONS survive — they simply stop belonging to
32589
+ * anything. Deleting a grouping must never delete what was grouped. */
32590
+ removeSiteZone: method(zod.z.object({ name: zod.z.string() }), zod.z.void(), {
32591
+ kind: "mutation",
32592
+ auth: "admin"
32593
+ }),
32594
+ /** Rename a zone, carrying its icon AND its membership.
32595
+ *
32596
+ * It exists so that renaming is not delete-then-recreate, which would drop
32597
+ * both — the exact orphaning that lost a location's description when the
32598
+ * two hung off different keys. Renaming onto an existing zone MERGES into
32599
+ * it and the target keeps its own icon. */
32600
+ renameSiteZone: method(zod.z.object({
32601
+ from: zod.z.string(),
32602
+ to: zod.z.string()
32603
+ }), zod.z.void(), {
32604
+ kind: "mutation",
32605
+ auth: "admin"
32606
+ }),
32607
+ /** Put a location in a zone, or take it out with `zone: null`.
32608
+ *
32609
+ * At most one zone per location, by construction: this writes one key, so
32610
+ * there is no state in which a room is in two places at once. The zone
32611
+ * need not exist yet — it is created empty-labelled from this call, the
32612
+ * same way `setLocationIcon` does not require a registered location. */
32613
+ setLocationZone: method(zod.z.object({
32614
+ location: zod.z.string(),
32615
+ zone: zod.z.string().nullable()
32616
+ }), zod.z.void(), {
32617
+ kind: "mutation",
32618
+ auth: "admin"
32619
+ }),
32444
32620
  /** Soft-disable / re-enable the device. Drivers consult
32445
32621
  * `BaseDevice.disabled` to gate lifecycle hooks. */
32446
32622
  setDisabled: method(zod.z.object({
@@ -54341,6 +54517,18 @@ var require_dist_BETx_5mH = __commonJS({
54341
54517
  addonId: null,
54342
54518
  access: "view"
54343
54519
  },
54520
+ "deviceManager.listLocationDescriptions": {
54521
+ capName: "device-manager",
54522
+ capScope: "system",
54523
+ addonId: null,
54524
+ access: "view"
54525
+ },
54526
+ "deviceManager.listLocationIcons": {
54527
+ capName: "device-manager",
54528
+ capScope: "system",
54529
+ addonId: null,
54530
+ access: "view"
54531
+ },
54344
54532
  "deviceManager.listLocations": {
54345
54533
  capName: "device-manager",
54346
54534
  capScope: "system",
@@ -54353,6 +54541,12 @@ var require_dist_BETx_5mH = __commonJS({
54353
54541
  addonId: null,
54354
54542
  access: "view"
54355
54543
  },
54544
+ "deviceManager.listSiteZones": {
54545
+ capName: "device-manager",
54546
+ capScope: "system",
54547
+ addonId: null,
54548
+ access: "view"
54549
+ },
54356
54550
  "deviceManager.listWrappersForCap": {
54357
54551
  capName: "device-manager",
54358
54552
  capScope: "system",
@@ -54437,12 +54631,24 @@ var require_dist_BETx_5mH = __commonJS({
54437
54631
  addonId: null,
54438
54632
  access: "delete"
54439
54633
  },
54634
+ "deviceManager.removeSiteZone": {
54635
+ capName: "device-manager",
54636
+ capScope: "system",
54637
+ addonId: null,
54638
+ access: "delete"
54639
+ },
54440
54640
  "deviceManager.renameLocation": {
54441
54641
  capName: "device-manager",
54442
54642
  capScope: "system",
54443
54643
  addonId: null,
54444
54644
  access: "create"
54445
54645
  },
54646
+ "deviceManager.renameSiteZone": {
54647
+ capName: "device-manager",
54648
+ capScope: "system",
54649
+ addonId: null,
54650
+ access: "create"
54651
+ },
54446
54652
  "deviceManager.runDeviceAction": {
54447
54653
  capName: "device-manager",
54448
54654
  capScope: "system",
@@ -54485,6 +54691,24 @@ var require_dist_BETx_5mH = __commonJS({
54485
54691
  addonId: null,
54486
54692
  access: "create"
54487
54693
  },
54694
+ "deviceManager.setLocationDescription": {
54695
+ capName: "device-manager",
54696
+ capScope: "system",
54697
+ addonId: null,
54698
+ access: "create"
54699
+ },
54700
+ "deviceManager.setLocationIcon": {
54701
+ capName: "device-manager",
54702
+ capScope: "system",
54703
+ addonId: null,
54704
+ access: "create"
54705
+ },
54706
+ "deviceManager.setLocationZone": {
54707
+ capName: "device-manager",
54708
+ capScope: "system",
54709
+ addonId: null,
54710
+ access: "create"
54711
+ },
54488
54712
  "deviceManager.setMetadata": {
54489
54713
  capName: "device-manager",
54490
54714
  capScope: "system",
@@ -54515,6 +54739,12 @@ var require_dist_BETx_5mH = __commonJS({
54515
54739
  addonId: null,
54516
54740
  access: "create"
54517
54741
  },
54742
+ "deviceManager.setSiteZone": {
54743
+ capName: "device-manager",
54744
+ capScope: "system",
54745
+ addonId: null,
54746
+ access: "create"
54747
+ },
54518
54748
  "deviceManager.setStreamProfileMap": {
54519
54749
  capName: "device-manager",
54520
54750
  capScope: "system",
@@ -61909,6 +62139,12 @@ var require_dist_BETx_5mH = __commonJS({
61909
62139
  return LegacyStorageLocationDefaultSchema;
61910
62140
  }
61911
62141
  });
62142
+ Object.defineProperty(exports, "LocationIconIdSchema", {
62143
+ enumerable: true,
62144
+ get: function() {
62145
+ return LocationIconIdSchema;
62146
+ }
62147
+ });
61912
62148
  Object.defineProperty(exports, "METHOD_ACCESS_MAP", {
61913
62149
  enumerable: true,
61914
62150
  get: function() {
@@ -62311,6 +62547,12 @@ var require_dist_BETx_5mH = __commonJS({
62311
62547
  return localNetworkCapability;
62312
62548
  }
62313
62549
  });
62550
+ Object.defineProperty(exports, "locationIconKey", {
62551
+ enumerable: true,
62552
+ get: function() {
62553
+ return locationIconKey;
62554
+ }
62555
+ });
62314
62556
  Object.defineProperty(exports, "logDestinationCapability", {
62315
62557
  enumerable: true,
62316
62558
  get: function() {
@@ -62479,6 +62721,12 @@ var require_dist_BETx_5mH = __commonJS({
62479
62721
  return settingsStoreCapability;
62480
62722
  }
62481
62723
  });
62724
+ Object.defineProperty(exports, "siteZoneKey", {
62725
+ enumerable: true,
62726
+ get: function() {
62727
+ return siteZoneKey;
62728
+ }
62729
+ });
62482
62730
  Object.defineProperty(exports, "sleep", {
62483
62731
  enumerable: true,
62484
62732
  get: function() {
@@ -62557,7 +62805,7 @@ var require_alerts_addon = __commonJS({
62557
62805
  [Symbol.toStringTag]: { value: "Module" }
62558
62806
  });
62559
62807
  require_chunk_Cek0wNdY();
62560
- var require_dist10 = require_dist_BETx_5mH();
62808
+ var require_dist10 = require_dist_Cz6h6Nc3();
62561
62809
  function selectExpired(alerts, cutoffMs) {
62562
62810
  return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
62563
62811
  }
@@ -63376,7 +63624,7 @@ var require_console_logging = __commonJS({
63376
63624
  [Symbol.toStringTag]: { value: "Module" }
63377
63625
  });
63378
63626
  require_chunk_Cek0wNdY();
63379
- var require_dist10 = require_dist_BETx_5mH();
63627
+ var require_dist10 = require_dist_Cz6h6Nc3();
63380
63628
  var require_formatter = require_formatter_DqAKDlvN();
63381
63629
  var LEVEL_RANK = {
63382
63630
  debug: 0,
@@ -63470,7 +63718,7 @@ var require_core_blocks_addon = __commonJS({
63470
63718
  "use strict";
63471
63719
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
63472
63720
  var require_chunk = require_chunk_Cek0wNdY();
63473
- var require_dist10 = require_dist_BETx_5mH();
63721
+ var require_dist10 = require_dist_Cz6h6Nc3();
63474
63722
  var node_crypto = __require("crypto");
63475
63723
  var node_fs_promises = __require("fs/promises");
63476
63724
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -64367,11 +64615,11 @@ var require_core_blocks = __commonJS({
64367
64615
  }
64368
64616
  });
64369
64617
 
64370
- // ../system/dist/retired-settings-keys-BnTBoLTv.js
64371
- var require_retired_settings_keys_BnTBoLTv = __commonJS({
64372
- "../system/dist/retired-settings-keys-BnTBoLTv.js"(exports) {
64618
+ // ../system/dist/retired-settings-keys-DBrIdOQp.js
64619
+ var require_retired_settings_keys_DBrIdOQp = __commonJS({
64620
+ "../system/dist/retired-settings-keys-DBrIdOQp.js"(exports) {
64373
64621
  "use strict";
64374
- var require_dist10 = require_dist_BETx_5mH();
64622
+ var require_dist10 = require_dist_Cz6h6Nc3();
64375
64623
  function settingsStoreIsAuthoritativeHere(env) {
64376
64624
  const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
64377
64625
  return raw === "" || raw === "hub";
@@ -66608,8 +66856,8 @@ var require_device_manager_addon = __commonJS({
66608
66856
  [Symbol.toStringTag]: { value: "Module" }
66609
66857
  });
66610
66858
  require_chunk_Cek0wNdY();
66611
- var require_dist10 = require_dist_BETx_5mH();
66612
- var require_retired_settings_keys = require_retired_settings_keys_BnTBoLTv();
66859
+ var require_dist10 = require_dist_Cz6h6Nc3();
66860
+ var require_retired_settings_keys = require_retired_settings_keys_DBrIdOQp();
66613
66861
  var node_crypto = __require("crypto");
66614
66862
  var _camstack_types_node = require_node();
66615
66863
  var JOB_HISTORY = 20;
@@ -70335,6 +70583,200 @@ var require_device_manager_addon = __commonJS({
70335
70583
  await pctx.settings.writeAddonStore({ locations: [...current, trimmed] });
70336
70584
  });
70337
70585
  }
70586
+ async function listLocationIcons(pctx) {
70587
+ const icons = (await pctx.metaStore.readStore()).locationIcons ?? {};
70588
+ return Object.entries(icons).map(([locationKey, icon]) => ({
70589
+ locationKey,
70590
+ icon
70591
+ })).toSorted((a, b) => a.locationKey.localeCompare(b.locationKey));
70592
+ }
70593
+ async function setLocationIcon(pctx, input) {
70594
+ const trimmed = input.location.trim();
70595
+ if (trimmed.length === 0) throw new Error("[device-manager] setLocationIcon: location must be non-empty");
70596
+ const key = require_dist10.locationIconKey(trimmed);
70597
+ await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
70598
+ const icons = (await pctx.metaStore.readStore()).locationIcons ?? {};
70599
+ if (input.icon === null) {
70600
+ if (!(key in icons)) return;
70601
+ const { [key]: _cleared, ...kept } = icons;
70602
+ await pctx.settings.writeAddonStore({ locationIcons: kept });
70603
+ return;
70604
+ }
70605
+ if (icons[key] === input.icon) return;
70606
+ await pctx.settings.writeAddonStore({ locationIcons: {
70607
+ ...icons,
70608
+ [key]: input.icon
70609
+ } });
70610
+ });
70611
+ }
70612
+ async function listLocationDescriptions(pctx) {
70613
+ const descriptions = (await pctx.metaStore.readStore()).locationDescriptions ?? {};
70614
+ return Object.entries(descriptions).map(([locationKey, description]) => ({
70615
+ locationKey,
70616
+ description
70617
+ })).toSorted((a, b) => a.locationKey.localeCompare(b.locationKey));
70618
+ }
70619
+ async function setLocationDescription(pctx, input) {
70620
+ const trimmed = input.location.trim();
70621
+ if (trimmed.length === 0) throw new Error("[device-manager] setLocationDescription: location must be non-empty");
70622
+ const key = require_dist10.locationIconKey(trimmed);
70623
+ const text = input.description === null ? "" : input.description.trim();
70624
+ await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
70625
+ const descriptions = (await pctx.metaStore.readStore()).locationDescriptions ?? {};
70626
+ if (text.length === 0) {
70627
+ if (!(key in descriptions)) return;
70628
+ const { [key]: _cleared, ...kept } = descriptions;
70629
+ await pctx.settings.writeAddonStore({ locationDescriptions: kept });
70630
+ return;
70631
+ }
70632
+ if (descriptions[key] === text) return;
70633
+ await pctx.settings.writeAddonStore({ locationDescriptions: {
70634
+ ...descriptions,
70635
+ [key]: text
70636
+ } });
70637
+ });
70638
+ }
70639
+ async function listSiteZones(pctx) {
70640
+ const store = await pctx.metaStore.readStore();
70641
+ const zones = store.siteZones ?? {};
70642
+ const membership = store.locationZones ?? {};
70643
+ const rooms = /* @__PURE__ */ new Map();
70644
+ for (const [locationKey, zoneKey] of Object.entries(membership)) {
70645
+ const list = rooms.get(zoneKey);
70646
+ if (list === void 0) rooms.set(zoneKey, [locationKey]);
70647
+ else list.push(locationKey);
70648
+ }
70649
+ return Object.entries(zones).map(([zoneKey, record]) => ({
70650
+ zoneKey,
70651
+ label: record.label,
70652
+ icon: record.icon ?? null,
70653
+ locationKeys: (rooms.get(zoneKey) ?? []).toSorted((a, b) => a.localeCompare(b))
70654
+ })).toSorted((a, b) => a.zoneKey.localeCompare(b.zoneKey));
70655
+ }
70656
+ async function setSiteZone(pctx, input) {
70657
+ const label = input.name.trim();
70658
+ if (label.length === 0) throw new Error("[device-manager] setSiteZone: name must be non-empty");
70659
+ const key = require_dist10.siteZoneKey(label);
70660
+ await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
70661
+ const zones = (await pctx.metaStore.readStore()).siteZones ?? {};
70662
+ const existing = zones[key];
70663
+ const icon = input.icon === void 0 ? existing?.icon : input.icon === null ? void 0 : input.icon;
70664
+ const next = icon === void 0 ? { label } : {
70665
+ label,
70666
+ icon
70667
+ };
70668
+ if (existing?.label === next.label && existing.icon === next.icon) return;
70669
+ await pctx.settings.writeAddonStore({ siteZones: {
70670
+ ...zones,
70671
+ [key]: next
70672
+ } });
70673
+ });
70674
+ }
70675
+ async function removeSiteZone(pctx, input) {
70676
+ const key = require_dist10.siteZoneKey(input.name);
70677
+ await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
70678
+ const store = await pctx.metaStore.readStore();
70679
+ const zones = store.siteZones ?? {};
70680
+ const membership = store.locationZones ?? {};
70681
+ const hadZone = key in zones;
70682
+ const orphaned = Object.entries(membership).filter(([, zoneKey]) => zoneKey === key);
70683
+ if (!hadZone && orphaned.length === 0) return;
70684
+ const { [key]: _dropped, ...keptZones } = zones;
70685
+ const keptMembership = Object.fromEntries(Object.entries(membership).filter(([, zoneKey]) => zoneKey !== key));
70686
+ await pctx.settings.writeAddonStore({
70687
+ ...hadZone ? { siteZones: keptZones } : {},
70688
+ ...orphaned.length > 0 ? { locationZones: keptMembership } : {}
70689
+ });
70690
+ });
70691
+ }
70692
+ async function renameSiteZone(pctx, input) {
70693
+ const toLabel = input.to.trim();
70694
+ if (toLabel.length === 0) throw new Error("[device-manager] renameSiteZone: target name must be non-empty");
70695
+ const fromKey = require_dist10.siteZoneKey(input.from);
70696
+ const toKey = require_dist10.siteZoneKey(toLabel);
70697
+ await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
70698
+ const store = await pctx.metaStore.readStore();
70699
+ const zones = store.siteZones ?? {};
70700
+ const moved = zones[fromKey];
70701
+ if (moved === void 0) return;
70702
+ const { [fromKey]: _dropped, ...rest } = zones;
70703
+ const target = rest[toKey];
70704
+ const nextZones = {
70705
+ ...rest,
70706
+ [toKey]: target ?? (moved.icon === void 0 ? { label: toLabel } : {
70707
+ label: toLabel,
70708
+ icon: moved.icon
70709
+ })
70710
+ };
70711
+ const membership = store.locationZones ?? {};
70712
+ const nextMembership = Object.fromEntries(Object.entries(membership).map(([locationKey, zoneKey]) => [locationKey, zoneKey === fromKey ? toKey : zoneKey]));
70713
+ await pctx.settings.writeAddonStore({
70714
+ siteZones: nextZones,
70715
+ locationZones: nextMembership
70716
+ });
70717
+ });
70718
+ }
70719
+ async function setLocationZone(pctx, input) {
70720
+ const location = input.location.trim();
70721
+ if (location.length === 0) throw new Error("[device-manager] setLocationZone: location must be non-empty");
70722
+ const locationKey = require_dist10.locationIconKey(location);
70723
+ const label = input.zone === null ? null : input.zone.trim();
70724
+ if (label !== null && label.length === 0) throw new Error("[device-manager] setLocationZone: zone must be non-empty or null");
70725
+ await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
70726
+ const store = await pctx.metaStore.readStore();
70727
+ const membership = store.locationZones ?? {};
70728
+ if (label === null) {
70729
+ if (!(locationKey in membership)) return;
70730
+ const { [locationKey]: _cleared, ...kept } = membership;
70731
+ await pctx.settings.writeAddonStore({ locationZones: kept });
70732
+ return;
70733
+ }
70734
+ const zoneKey = require_dist10.siteZoneKey(label);
70735
+ const zones = store.siteZones ?? {};
70736
+ const needsZone = !(zoneKey in zones);
70737
+ if (membership[locationKey] === zoneKey && !needsZone) return;
70738
+ await pctx.settings.writeAddonStore({
70739
+ ...needsZone ? { siteZones: {
70740
+ ...zones,
70741
+ [zoneKey]: { label }
70742
+ } } : {},
70743
+ locationZones: {
70744
+ ...membership,
70745
+ [locationKey]: zoneKey
70746
+ }
70747
+ });
70748
+ });
70749
+ }
70750
+ function renameLocationZone(membership, fromKey, toKey) {
70751
+ const moved = membership[fromKey];
70752
+ if (moved === void 0) return null;
70753
+ if (fromKey === toKey) return null;
70754
+ const { [fromKey]: _dropped, ...rest } = membership;
70755
+ return toKey in rest ? rest : {
70756
+ ...rest,
70757
+ [toKey]: moved
70758
+ };
70759
+ }
70760
+ function renameLocationDescription(descriptions, fromKey, toKey) {
70761
+ const moved = descriptions[fromKey];
70762
+ if (moved === void 0) return null;
70763
+ if (fromKey === toKey) return null;
70764
+ const { [fromKey]: _dropped, ...rest } = descriptions;
70765
+ return toKey in rest ? rest : {
70766
+ ...rest,
70767
+ [toKey]: moved
70768
+ };
70769
+ }
70770
+ function renameLocationIcon(icons, fromKey, toKey) {
70771
+ const moved = icons[fromKey];
70772
+ if (moved === void 0) return null;
70773
+ if (fromKey === toKey) return null;
70774
+ const { [fromKey]: _dropped, ...rest } = icons;
70775
+ return toKey in rest ? rest : {
70776
+ ...rest,
70777
+ [toKey]: moved
70778
+ };
70779
+ }
70338
70780
  async function renameLocation(pctx, input) {
70339
70781
  const from = input.from.trim();
70340
70782
  const to = input.to.trim();
@@ -70343,13 +70785,23 @@ var require_device_manager_addon = __commonJS({
70343
70785
  const fromKey = from.toLowerCase();
70344
70786
  const toKey = to.toLowerCase();
70345
70787
  await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
70346
- const current = (await pctx.metaStore.readStore()).locations ?? [];
70347
- if (!current.some((l) => l.trim().toLowerCase() === fromKey)) return;
70788
+ const store = await pctx.metaStore.readStore();
70789
+ const current = store.locations ?? [];
70790
+ const registered = current.some((l) => l.trim().toLowerCase() === fromKey);
70791
+ const movedIcons = renameLocationIcon(store.locationIcons ?? {}, fromKey, toKey);
70792
+ const movedDescriptions = renameLocationDescription(store.locationDescriptions ?? {}, fromKey, toKey);
70793
+ const movedZones = renameLocationZone(store.locationZones ?? {}, fromKey, toKey);
70794
+ if (!registered && movedIcons === null && movedDescriptions === null && movedZones === null) return;
70348
70795
  const kept = current.filter((l) => {
70349
70796
  const key = l.trim().toLowerCase();
70350
70797
  return key !== fromKey && key !== toKey;
70351
70798
  });
70352
- await pctx.settings.writeAddonStore({ locations: [...kept, to] });
70799
+ await pctx.settings.writeAddonStore({
70800
+ ...registered ? { locations: [...kept, to] } : {},
70801
+ ...movedIcons !== null ? { locationIcons: movedIcons } : {},
70802
+ ...movedDescriptions !== null ? { locationDescriptions: movedDescriptions } : {},
70803
+ ...movedZones !== null ? { locationZones: movedZones } : {}
70804
+ });
70353
70805
  });
70354
70806
  const moved = await pctx.metaStore.withMetaWriteLock(async () => {
70355
70807
  const out = [];
@@ -70387,10 +70839,27 @@ var require_device_manager_addon = __commonJS({
70387
70839
  const trimmed = input.name.trim();
70388
70840
  if (trimmed.length === 0) return;
70389
70841
  await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
70390
- const current = (await pctx.metaStore.readStore()).locations ?? [];
70391
- const remaining = current.filter((l) => l.toLowerCase() !== trimmed.toLowerCase());
70392
- if (remaining.length === current.length) return;
70393
- await pctx.settings.writeAddonStore({ locations: remaining });
70842
+ const store = await pctx.metaStore.readStore();
70843
+ const current = store.locations ?? [];
70844
+ const key = require_dist10.locationIconKey(trimmed);
70845
+ const remaining = current.filter((l) => l.toLowerCase() !== key);
70846
+ const droppedLabel = remaining.length !== current.length;
70847
+ const icons = store.locationIcons ?? {};
70848
+ const droppedIcon = key in icons;
70849
+ const descriptions = store.locationDescriptions ?? {};
70850
+ const droppedDescription = key in descriptions;
70851
+ const membership = store.locationZones ?? {};
70852
+ const droppedZone = key in membership;
70853
+ if (!droppedLabel && !droppedIcon && !droppedDescription && !droppedZone) return;
70854
+ const { [key]: _removedIcon, ...keptIcons } = icons;
70855
+ const { [key]: _removedDescription, ...keptDescriptions } = descriptions;
70856
+ const { [key]: _removedZone, ...keptMembership } = membership;
70857
+ await pctx.settings.writeAddonStore({
70858
+ ...droppedLabel ? { locations: remaining } : {},
70859
+ ...droppedIcon ? { locationIcons: keptIcons } : {},
70860
+ ...droppedDescription ? { locationDescriptions: keptDescriptions } : {},
70861
+ ...droppedZone ? { locationZones: keptMembership } : {}
70862
+ });
70394
70863
  });
70395
70864
  if (input.cascade !== true) return;
70396
70865
  const cleared = await pctx.metaStore.withMetaWriteLock(async () => {
@@ -70481,12 +70950,66 @@ var require_device_manager_addon = __commonJS({
70481
70950
  const nextDeviceId = raw["nextDeviceId"];
70482
70951
  const locations = raw["locations"];
70483
70952
  const roleDisplayDefaults = raw["roleDisplayDefaults"];
70953
+ const locationIcons = decodeLocationIcons(raw["locationIcons"]);
70954
+ const locationDescriptions = decodeLocationDescriptions(raw["locationDescriptions"]);
70955
+ const siteZones = decodeSiteZones(raw["siteZones"]);
70956
+ const locationZones = decodeStringMap(raw["locationZones"]);
70484
70957
  return {
70485
70958
  ...typeof nextDeviceId === "number" && Number.isFinite(nextDeviceId) ? { nextDeviceId } : {},
70486
70959
  ...Array.isArray(locations) ? { locations: locations.filter((l) => typeof l === "string") } : {},
70487
- ...isRoleDisplayDefaults(roleDisplayDefaults) ? { roleDisplayDefaults } : {}
70960
+ ...isRoleDisplayDefaults(roleDisplayDefaults) ? { roleDisplayDefaults } : {},
70961
+ ...locationIcons !== null ? { locationIcons } : {},
70962
+ ...locationDescriptions !== null ? { locationDescriptions } : {},
70963
+ ...siteZones !== null ? { siteZones } : {},
70964
+ ...locationZones !== null ? { locationZones } : {}
70488
70965
  };
70489
70966
  }
70967
+ function decodeLocationIcons(raw) {
70968
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
70969
+ const out = {};
70970
+ for (const [key, value] of Object.entries(raw)) {
70971
+ const parsed = require_dist10.LocationIconIdSchema.safeParse(value);
70972
+ if (!parsed.success) continue;
70973
+ out[key] = parsed.data;
70974
+ }
70975
+ return out;
70976
+ }
70977
+ function decodeLocationDescriptions(raw) {
70978
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
70979
+ const out = {};
70980
+ for (const [key, value] of Object.entries(raw)) {
70981
+ if (typeof value !== "string") continue;
70982
+ const text = value.trim();
70983
+ if (text.length === 0) continue;
70984
+ out[key] = text.slice(0, 400);
70985
+ }
70986
+ return out;
70987
+ }
70988
+ function decodeSiteZones(raw) {
70989
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
70990
+ const out = {};
70991
+ for (const [key, value] of Object.entries(raw)) {
70992
+ if (value === null || typeof value !== "object" || Array.isArray(value)) continue;
70993
+ const rec = value;
70994
+ const label = typeof rec["label"] === "string" ? rec["label"].trim() : "";
70995
+ if (label.length === 0) continue;
70996
+ const icon = require_dist10.LocationIconIdSchema.safeParse(rec["icon"]);
70997
+ out[key] = icon.success ? {
70998
+ label,
70999
+ icon: icon.data
71000
+ } : { label };
71001
+ }
71002
+ return out;
71003
+ }
71004
+ function decodeStringMap(raw) {
71005
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
71006
+ const out = {};
71007
+ for (const [key, value] of Object.entries(raw)) {
71008
+ if (typeof value !== "string" || value.trim().length === 0) continue;
71009
+ out[key] = value;
71010
+ }
71011
+ return out;
71012
+ }
70490
71013
  function isRoleDisplayDefaults(value) {
70491
71014
  return value !== null && typeof value === "object" && !Array.isArray(value);
70492
71015
  }
@@ -72127,6 +72650,15 @@ var require_device_manager_addon = __commonJS({
72127
72650
  addLocation: (input) => addLocation(pctx, input),
72128
72651
  removeLocation: (input) => removeLocation(pctx, input),
72129
72652
  renameLocation: (input) => renameLocation(pctx, input),
72653
+ listLocationIcons: () => listLocationIcons(pctx),
72654
+ setLocationIcon: (input) => setLocationIcon(pctx, input),
72655
+ listLocationDescriptions: () => listLocationDescriptions(pctx),
72656
+ setLocationDescription: (input) => setLocationDescription(pctx, input),
72657
+ listSiteZones: () => listSiteZones(pctx),
72658
+ setSiteZone: (input) => setSiteZone(pctx, input),
72659
+ removeSiteZone: (input) => removeSiteZone(pctx, input),
72660
+ renameSiteZone: (input) => renameSiteZone(pctx, input),
72661
+ setLocationZone: (input) => setLocationZone(pctx, input),
72130
72662
  listPersistedByAddon: (input) => listPersistedByAddon(pctx, input),
72131
72663
  listAll: (input) => listAll(pctx, input),
72132
72664
  getDevice: (input) => getDevice(pctx, input),
@@ -72333,7 +72865,7 @@ var require_hub_forwarder = __commonJS({
72333
72865
  [Symbol.toStringTag]: { value: "Module" }
72334
72866
  });
72335
72867
  require_chunk_Cek0wNdY();
72336
- var require_dist10 = require_dist_BETx_5mH();
72868
+ var require_dist10 = require_dist_Cz6h6Nc3();
72337
72869
  var require_formatter = require_formatter_DqAKDlvN();
72338
72870
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
72339
72871
  var HubForwarderDestination = class {
@@ -72470,7 +73002,7 @@ var require_liveness_monitor_addon = __commonJS({
72470
73002
  "use strict";
72471
73003
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
72472
73004
  require_chunk_Cek0wNdY();
72473
- var require_dist10 = require_dist_BETx_5mH();
73005
+ var require_dist10 = require_dist_Cz6h6Nc3();
72474
73006
  var NO_DEVICES = "liveness:no-devices";
72475
73007
  var ALL_OFFLINE = "liveness:all-devices-offline";
72476
73008
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -72666,7 +73198,7 @@ var require_local_auth_addon = __commonJS({
72666
73198
  [Symbol.toStringTag]: { value: "Module" }
72667
73199
  });
72668
73200
  var require_chunk = require_chunk_Cek0wNdY();
72669
- var require_dist10 = require_dist_BETx_5mH();
73201
+ var require_dist10 = require_dist_Cz6h6Nc3();
72670
73202
  var node_crypto = __require("crypto");
72671
73203
  node_crypto = require_chunk.__toESM(node_crypto);
72672
73204
  var crypto$1 = __require("crypto");
@@ -80479,7 +81011,7 @@ var require_loki_logging = __commonJS({
80479
81011
  [Symbol.toStringTag]: { value: "Module" }
80480
81012
  });
80481
81013
  require_chunk_Cek0wNdY();
80482
- var require_dist10 = require_dist_BETx_5mH();
81014
+ var require_dist10 = require_dist_Cz6h6Nc3();
80483
81015
  function sanitizeLabelName(raw) {
80484
81016
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
80485
81017
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -81066,7 +81598,7 @@ var require_native_metrics_addon = __commonJS({
81066
81598
  [Symbol.toStringTag]: { value: "Module" }
81067
81599
  });
81068
81600
  var require_chunk = require_chunk_Cek0wNdY();
81069
- var require_dist10 = require_dist_BETx_5mH();
81601
+ var require_dist10 = require_dist_Cz6h6Nc3();
81070
81602
  var node_fs_promises = __require("fs/promises");
81071
81603
  var node_fs = __require("fs");
81072
81604
  node_fs = require_chunk.__toESM(node_fs);
@@ -83819,7 +84351,7 @@ var require_filesystem_storage_addon = __commonJS({
83819
84351
  [Symbol.toStringTag]: { value: "Module" }
83820
84352
  });
83821
84353
  var require_chunk = require_chunk_Cek0wNdY();
83822
- var require_dist10 = require_dist_BETx_5mH();
84354
+ var require_dist10 = require_dist_Cz6h6Nc3();
83823
84355
  var node_crypto = __require("crypto");
83824
84356
  var node_fs_promises = __require("fs/promises");
83825
84357
  var node_path = __require("path");
@@ -85046,8 +85578,8 @@ var require_sqlite_settings_addon = __commonJS({
85046
85578
  [Symbol.toStringTag]: { value: "Module" }
85047
85579
  });
85048
85580
  var require_chunk = require_chunk_Cek0wNdY();
85049
- var require_dist10 = require_dist_BETx_5mH();
85050
- var require_retired_settings_keys = require_retired_settings_keys_BnTBoLTv();
85581
+ var require_dist10 = require_dist_Cz6h6Nc3();
85582
+ var require_retired_settings_keys = require_retired_settings_keys_DBrIdOQp();
85051
85583
  var require_wal_checkpoint_policy = require_wal_checkpoint_policy_CCJZngds();
85052
85584
  var node_crypto = __require("crypto");
85053
85585
  var node_path = __require("path");
@@ -87834,7 +88366,7 @@ var require_storage_orchestrator_addon = __commonJS({
87834
88366
  [Symbol.toStringTag]: { value: "Module" }
87835
88367
  });
87836
88368
  var require_chunk = require_chunk_Cek0wNdY();
87837
- var require_dist10 = require_dist_BETx_5mH();
88369
+ var require_dist10 = require_dist_Cz6h6Nc3();
87838
88370
  var require_hub_cap_forward = require_hub_cap_forward_DmHNjbB0();
87839
88371
  var zod = require_zod();
87840
88372
  var node_crypto = __require("crypto");
@@ -91561,7 +92093,7 @@ var require_system_config_addon = __commonJS({
91561
92093
  [Symbol.toStringTag]: { value: "Module" }
91562
92094
  });
91563
92095
  require_chunk_Cek0wNdY();
91564
- var require_dist10 = require_dist_BETx_5mH();
92096
+ var require_dist10 = require_dist_Cz6h6Nc3();
91565
92097
  var SECTION_TITLES = {
91566
92098
  server: "Server",
91567
92099
  auth: "Authentication"
@@ -109622,7 +110154,7 @@ var require_winston_logging = __commonJS({
109622
110154
  [Symbol.toStringTag]: { value: "Module" }
109623
110155
  });
109624
110156
  var require_chunk = require_chunk_Cek0wNdY();
109625
- var require_dist10 = require_dist_BETx_5mH();
110157
+ var require_dist10 = require_dist_Cz6h6Nc3();
109626
110158
  var require_formatter = require_formatter_DqAKDlvN();
109627
110159
  var node_path = __require("path");
109628
110160
  node_path = require_chunk.__toESM(node_path);
@@ -135889,7 +136421,7 @@ var require_dist3 = __commonJS({
135889
136421
  "use strict";
135890
136422
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
135891
136423
  var require_chunk = require_chunk_Cek0wNdY();
135892
- var require_dist10 = require_dist_BETx_5mH();
136424
+ var require_dist10 = require_dist_Cz6h6Nc3();
135893
136425
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
135894
136426
  require_alerts();
135895
136427
  var require_formatter = require_formatter_DqAKDlvN();
@@ -225140,6 +225672,58 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
225140
225672
  unexposeDevice: require_sleep.method(UnexposeInputSchema, zod.z.void(), { kind: "mutation" })
225141
225673
  }
225142
225674
  };
225675
+ var LOCATION_ICON_IDS = [
225676
+ "map-pin",
225677
+ "house",
225678
+ "door-open",
225679
+ "sofa",
225680
+ "utensils",
225681
+ "bed-double",
225682
+ "bath",
225683
+ "briefcase",
225684
+ "car",
225685
+ "circle-parking",
225686
+ "route",
225687
+ "trees",
225688
+ "umbrella",
225689
+ "waves-ladder",
225690
+ "fence",
225691
+ "package",
225692
+ "warehouse",
225693
+ "store",
225694
+ "sun",
225695
+ "radar"
225696
+ ];
225697
+ var LOCATION_ICON_FALLBACK = "map-pin";
225698
+ var LocationIconIdSchema = zod.z.enum(LOCATION_ICON_IDS);
225699
+ var LocationIconAssignmentSchema = zod.z.object({
225700
+ locationKey: zod.z.string(),
225701
+ icon: LocationIconIdSchema
225702
+ });
225703
+ function locationIconKey(label) {
225704
+ return label.trim().toLowerCase();
225705
+ }
225706
+ function resolveLocationIcon(value) {
225707
+ const parsed = LocationIconIdSchema.safeParse(value);
225708
+ return parsed.success ? parsed.data : LOCATION_ICON_FALLBACK;
225709
+ }
225710
+ var LOCATION_DESCRIPTION_MAX = 400;
225711
+ var LocationDescriptionAssignmentSchema = zod.z.object({
225712
+ locationKey: zod.z.string(),
225713
+ description: zod.z.string()
225714
+ });
225715
+ var SiteZoneSchema = zod.z.object({
225716
+ zoneKey: zod.z.string(),
225717
+ label: zod.z.string(),
225718
+ icon: LocationIconIdSchema.nullable(),
225719
+ /** Folded location keys, sorted. A location listed here need not be
225720
+ * REGISTERED — devices carry labels nobody registered, and those rooms are
225721
+ * the ones that most want grouping. */
225722
+ locationKeys: zod.z.array(zod.z.string())
225723
+ });
225724
+ function siteZoneKey(label) {
225725
+ return label.trim().toLowerCase();
225726
+ }
225143
225727
  var ProviderStatusSchema = zod.z.object({
225144
225728
  connected: zod.z.boolean(),
225145
225729
  deviceCount: zod.z.number(),
@@ -225789,7 +226373,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
225789
226373
  * reference this label keep their `meta.location` value — the
225790
226374
  * registry is purely a suggestion list. To purge from devices,
225791
226375
  * iterate them and call `setLocation(deviceId, null)`
225792
- * separately (or pass `cascade: true` to do it in one shot). */
226376
+ * separately (or pass `cascade: true` to do it in one shot).
226377
+ *
226378
+ * The room's ICON goes with it, always — a key left behind survives in a
226379
+ * map nobody lists, and a room later re-created under the same name would
226380
+ * silently inherit an icon the operator never chose for it. */
225793
226381
  removeLocation: require_sleep.method(zod.z.object({
225794
226382
  name: zod.z.string(),
225795
226383
  cascade: zod.z.boolean().optional()
@@ -225810,7 +226398,13 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
225810
226398
  * a suggestion list, and the devices that most need a rename are exactly
225811
226399
  * the ones carrying a label nobody registered. `to` renames onto an
225812
226400
  * existing room by MERGING into it, leaving no duplicate label. An empty
225813
- * `to` throws before anything is written. */
226401
+ * `to` throws before anything is written.
226402
+ *
226403
+ * The room's ICON travels with the rename, including when `from` was
226404
+ * never registered — otherwise it orphans on a key no location carries
226405
+ * any more. A merge keeps the TARGET's icon when it has one: renaming
226406
+ * "Sala" into "Salone" moves devices into Salone, it does not restyle
226407
+ * it. */
225814
226408
  renameLocation: require_sleep.method(zod.z.object({
225815
226409
  from: zod.z.string(),
225816
226410
  to: zod.z.string()
@@ -225818,6 +226412,127 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
225818
226412
  kind: "mutation",
225819
226413
  auth: "admin"
225820
226414
  }),
226415
+ /** Every location that carries an ICON, as `(locationKey, icon)` pairs.
226416
+ *
226417
+ * ADDITIVE, deliberately: `listLocations` keeps returning `string[]` —
226418
+ * every consumer of it (the Device Info autocomplete, the HA adoption
226419
+ * import, the recorder) reads a list of labels and none of them wants a
226420
+ * shape change. A surface that wants icons asks for them and joins.
226421
+ *
226422
+ * Only assignments that EXIST are returned — a location with no icon is
226423
+ * absent, not `map-pin`. The fallback belongs to the renderer
226424
+ * (`resolveLocationIcon`), not to the store, so "the operator chose the
226425
+ * pin" and "the operator chose nothing" stay distinguishable.
226426
+ *
226427
+ * The join key is `locationIconKey(label)` (trimmed, lower-cased) — the
226428
+ * same equality the registry uses everywhere. Never the operator's
226429
+ * casing: a case-only rename is a real edit here, and a map keyed on the
226430
+ * displayed spelling would orphan its entry on exactly that edit. */
226431
+ listLocationIcons: require_sleep.method(zod.z.void(), zod.z.array(LocationIconAssignmentSchema)),
226432
+ /** Set (or clear) the icon of a location label. `icon: null` removes the
226433
+ * assignment. The label need NOT be registered — the registry is a
226434
+ * suggestion list and devices carry labels nobody registered — but an
226435
+ * empty / whitespace-only label throws. Idempotent.
226436
+ *
226437
+ * The icon vocabulary is CLOSED (`LOCATION_ICON_IDS`) because the admin UI
226438
+ * and the viewer draw from different icon modules; see
226439
+ * `device/location-icon.ts` for the list and for what a surface that
226440
+ * cannot draw a member must do (fall back to `map-pin`, never a hole). */
226441
+ setLocationIcon: require_sleep.method(zod.z.object({
226442
+ location: zod.z.string(),
226443
+ icon: LocationIconIdSchema.nullable()
226444
+ }), zod.z.void(), {
226445
+ kind: "mutation",
226446
+ auth: "admin"
226447
+ }),
226448
+ /** Every location that carries a DESCRIPTION — what the place LOOKS like,
226449
+ * in plain words, given to the vision model alongside the pictures so it
226450
+ * knows what it is looking at. A driveway of cobbles between a brick wall
226451
+ * and three parking spaces reads very differently from a back garden.
226452
+ *
226453
+ * Same additive shape and same join key as `listLocationIcons`, and that
226454
+ * is the point of it being here at all: this lived in the post-analysis
226455
+ * addon's own settings, keyed by a hand-typed label matched exactly and
226456
+ * case-sensitively, while the icon hung off the location itself. A rename
226457
+ * carried the icon and silently orphaned the description. One location,
226458
+ * one authority, one key.
226459
+ *
226460
+ * Absent means nobody wrote one — the digest then tells the model only the
226461
+ * location NAME, exactly as it did before descriptions existed. */
226462
+ listLocationDescriptions: require_sleep.method(zod.z.void(), zod.z.array(LocationDescriptionAssignmentSchema)),
226463
+ /** Set (or clear) a location's description. `description: null`, or a
226464
+ * blank string, removes it — an empty description and no description are
226465
+ * the same thing to every reader, and storing the first would send an
226466
+ * empty line to the model.
226467
+ *
226468
+ * Like `setLocationIcon`, the label need NOT be registered, and an
226469
+ * empty / whitespace-only LABEL throws. */
226470
+ setLocationDescription: require_sleep.method(zod.z.object({
226471
+ location: zod.z.string(),
226472
+ description: zod.z.string().max(400).nullable()
226473
+ }), zod.z.void(), {
226474
+ kind: "mutation",
226475
+ auth: "admin"
226476
+ }),
226477
+ /** Every SITE ZONE — a named part of the property that groups locations
226478
+ * ("Zona notte" holds the bedrooms). One call answers the whole picture:
226479
+ * key, the label as typed, the icon, and the locations inside.
226480
+ *
226481
+ * Together, because a surface that asked for membership separately could
226482
+ * draw a zone whose rooms it has not fetched — and the strip that renders
226483
+ * these puts zones BEFORE locations, so a half-known zone is the first
226484
+ * thing an operator sees.
226485
+ *
226486
+ * Nothing here touches a device: a camera's zone is derived from its
226487
+ * location, never stored on the row. Grouping rooms must not rewrite the
226488
+ * fleet. */
226489
+ listSiteZones: require_sleep.method(zod.z.void(), zod.z.array(SiteZoneSchema)),
226490
+ /** Create a zone, or set the icon of one that exists. `icon: null` clears
226491
+ * the icon and KEEPS the zone — a zone with no icon is a normal zone, and
226492
+ * conflating "no icon" with "no zone" would delete membership on a
226493
+ * mis-click.
226494
+ *
226495
+ * Idempotent, and the label is stored as typed: the key is folded, so
226496
+ * re-running this with different casing re-labels rather than duplicating. */
226497
+ setSiteZone: require_sleep.method(zod.z.object({
226498
+ name: zod.z.string(),
226499
+ icon: LocationIconIdSchema.nullable().optional()
226500
+ }), zod.z.void(), {
226501
+ kind: "mutation",
226502
+ auth: "admin"
226503
+ }),
226504
+ /** Delete a zone. Its LOCATIONS survive — they simply stop belonging to
226505
+ * anything. Deleting a grouping must never delete what was grouped. */
226506
+ removeSiteZone: require_sleep.method(zod.z.object({ name: zod.z.string() }), zod.z.void(), {
226507
+ kind: "mutation",
226508
+ auth: "admin"
226509
+ }),
226510
+ /** Rename a zone, carrying its icon AND its membership.
226511
+ *
226512
+ * It exists so that renaming is not delete-then-recreate, which would drop
226513
+ * both — the exact orphaning that lost a location's description when the
226514
+ * two hung off different keys. Renaming onto an existing zone MERGES into
226515
+ * it and the target keeps its own icon. */
226516
+ renameSiteZone: require_sleep.method(zod.z.object({
226517
+ from: zod.z.string(),
226518
+ to: zod.z.string()
226519
+ }), zod.z.void(), {
226520
+ kind: "mutation",
226521
+ auth: "admin"
226522
+ }),
226523
+ /** Put a location in a zone, or take it out with `zone: null`.
226524
+ *
226525
+ * At most one zone per location, by construction: this writes one key, so
226526
+ * there is no state in which a room is in two places at once. The zone
226527
+ * need not exist yet — it is created empty-labelled from this call, the
226528
+ * same way `setLocationIcon` does not require a registered location. */
226529
+ setLocationZone: require_sleep.method(zod.z.object({
226530
+ location: zod.z.string(),
226531
+ zone: zod.z.string().nullable()
226532
+ }), zod.z.void(), {
226533
+ kind: "mutation",
226534
+ auth: "admin"
226535
+ }),
225821
226536
  /** Soft-disable / re-enable the device. Drivers consult
225822
226537
  * `BaseDevice.disabled` to gate lifecycle hooks. */
225823
226538
  setDisabled: require_sleep.method(zod.z.object({
@@ -252295,6 +253010,18 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252295
253010
  addonId: null,
252296
253011
  access: "view"
252297
253012
  },
253013
+ "deviceManager.listLocationDescriptions": {
253014
+ capName: "device-manager",
253015
+ capScope: "system",
253016
+ addonId: null,
253017
+ access: "view"
253018
+ },
253019
+ "deviceManager.listLocationIcons": {
253020
+ capName: "device-manager",
253021
+ capScope: "system",
253022
+ addonId: null,
253023
+ access: "view"
253024
+ },
252298
253025
  "deviceManager.listLocations": {
252299
253026
  capName: "device-manager",
252300
253027
  capScope: "system",
@@ -252307,6 +253034,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252307
253034
  addonId: null,
252308
253035
  access: "view"
252309
253036
  },
253037
+ "deviceManager.listSiteZones": {
253038
+ capName: "device-manager",
253039
+ capScope: "system",
253040
+ addonId: null,
253041
+ access: "view"
253042
+ },
252310
253043
  "deviceManager.listWrappersForCap": {
252311
253044
  capName: "device-manager",
252312
253045
  capScope: "system",
@@ -252391,12 +253124,24 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252391
253124
  addonId: null,
252392
253125
  access: "delete"
252393
253126
  },
253127
+ "deviceManager.removeSiteZone": {
253128
+ capName: "device-manager",
253129
+ capScope: "system",
253130
+ addonId: null,
253131
+ access: "delete"
253132
+ },
252394
253133
  "deviceManager.renameLocation": {
252395
253134
  capName: "device-manager",
252396
253135
  capScope: "system",
252397
253136
  addonId: null,
252398
253137
  access: "create"
252399
253138
  },
253139
+ "deviceManager.renameSiteZone": {
253140
+ capName: "device-manager",
253141
+ capScope: "system",
253142
+ addonId: null,
253143
+ access: "create"
253144
+ },
252400
253145
  "deviceManager.runDeviceAction": {
252401
253146
  capName: "device-manager",
252402
253147
  capScope: "system",
@@ -252439,6 +253184,24 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252439
253184
  addonId: null,
252440
253185
  access: "create"
252441
253186
  },
253187
+ "deviceManager.setLocationDescription": {
253188
+ capName: "device-manager",
253189
+ capScope: "system",
253190
+ addonId: null,
253191
+ access: "create"
253192
+ },
253193
+ "deviceManager.setLocationIcon": {
253194
+ capName: "device-manager",
253195
+ capScope: "system",
253196
+ addonId: null,
253197
+ access: "create"
253198
+ },
253199
+ "deviceManager.setLocationZone": {
253200
+ capName: "device-manager",
253201
+ capScope: "system",
253202
+ addonId: null,
253203
+ access: "create"
253204
+ },
252442
253205
  "deviceManager.setMetadata": {
252443
253206
  capName: "device-manager",
252444
253207
  capScope: "system",
@@ -252469,6 +253232,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252469
253232
  addonId: null,
252470
253233
  access: "create"
252471
253234
  },
253235
+ "deviceManager.setSiteZone": {
253236
+ capName: "device-manager",
253237
+ capScope: "system",
253238
+ addonId: null,
253239
+ access: "create"
253240
+ },
252472
253241
  "deviceManager.setStreamProfileMap": {
252473
253242
  capName: "device-manager",
252474
253243
  capScope: "system",
@@ -260005,6 +260774,15 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
260005
260774
  addLocation: (input) => dispatch("deviceManager", "addLocation", "mutation", input),
260006
260775
  removeLocation: (input) => dispatch("deviceManager", "removeLocation", "mutation", input),
260007
260776
  renameLocation: (input) => dispatch("deviceManager", "renameLocation", "mutation", input),
260777
+ listLocationIcons: (input) => dispatch("deviceManager", "listLocationIcons", "query", input),
260778
+ setLocationIcon: (input) => dispatch("deviceManager", "setLocationIcon", "mutation", input),
260779
+ listLocationDescriptions: (input) => dispatch("deviceManager", "listLocationDescriptions", "query", input),
260780
+ setLocationDescription: (input) => dispatch("deviceManager", "setLocationDescription", "mutation", input),
260781
+ listSiteZones: (input) => dispatch("deviceManager", "listSiteZones", "query", input),
260782
+ setSiteZone: (input) => dispatch("deviceManager", "setSiteZone", "mutation", input),
260783
+ removeSiteZone: (input) => dispatch("deviceManager", "removeSiteZone", "mutation", input),
260784
+ renameSiteZone: (input) => dispatch("deviceManager", "renameSiteZone", "mutation", input),
260785
+ setLocationZone: (input) => dispatch("deviceManager", "setLocationZone", "mutation", input),
260008
260786
  listPersistedByAddon: (input) => dispatch("deviceManager", "listPersistedByAddon", "query", input),
260009
260787
  listAll: (input) => dispatch("deviceManager", "listAll", "query", input),
260010
260788
  getChildren: (input) => dispatch("deviceManager", "getChildren", "query", input),
@@ -263552,6 +264330,9 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
263552
264330
  exports.LEGACY_READ_ONLY_CONFIG_KEY = LEGACY_READ_ONLY_CONFIG_KEY;
263553
264331
  exports.LOAD_CONTRIBUTION_ATTRIBUTIONS = LOAD_CONTRIBUTION_ATTRIBUTIONS;
263554
264332
  exports.LOAD_CONTRIBUTION_ROLES = LOAD_CONTRIBUTION_ROLES;
264333
+ exports.LOCATION_DESCRIPTION_MAX = LOCATION_DESCRIPTION_MAX;
264334
+ exports.LOCATION_ICON_FALLBACK = LOCATION_ICON_FALLBACK;
264335
+ exports.LOCATION_ICON_IDS = LOCATION_ICON_IDS;
263555
264336
  exports.LOG_CHANNEL_TICK_MS = LOG_CHANNEL_TICK_MS;
263556
264337
  exports.LOG_LEVEL_RANK = LOG_LEVEL_RANK;
263557
264338
  exports.LTS_SCOPE_CLASS_SEPARATOR = LTS_SCOPE_CLASS_SEPARATOR;
@@ -263592,6 +264373,9 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
263592
264373
  exports.LlmUsageSchema = LlmUsageSchema;
263593
264374
  exports.LoadContributionSchema = LoadContributionSchema;
263594
264375
  exports.LocateSegmentResultSchema = LocateSegmentResultSchema;
264376
+ exports.LocationDescriptionAssignmentSchema = LocationDescriptionAssignmentSchema;
264377
+ exports.LocationIconAssignmentSchema = LocationIconAssignmentSchema;
264378
+ exports.LocationIconIdSchema = LocationIconIdSchema;
263595
264379
  exports.LocationStatSchema = LocationStatSchema;
263596
264380
  exports.LockControlStatusSchema = LockControlStatusSchema;
263597
264381
  exports.LockStateSchema = LockStateSchema;
@@ -264067,6 +264851,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
264067
264851
  exports.SiteLocationSchema = SiteLocationSchema;
264068
264852
  exports.SiteLocationSourceSchema = SiteLocationSourceSchema;
264069
264853
  exports.SiteLocationStatusSchema = SiteLocationStatusSchema;
264854
+ exports.SiteZoneSchema = SiteZoneSchema;
264070
264855
  exports.SmokeStatusSchema = SmokeStatusSchema;
264071
264856
  exports.SmtpStatusSchema = SmtpStatusSchema;
264072
264857
  exports.SnapshotImageSchema = SnapshotImageSchema;
@@ -264491,6 +265276,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
264491
265276
  exports.llmRuntimeCapability = llmRuntimeCapability;
264492
265277
  exports.loadContributionCapability = loadContributionCapability;
264493
265278
  exports.localNetworkCapability = localNetworkCapability;
265279
+ exports.locationIconKey = locationIconKey;
264494
265280
  exports.locationSimilarity = locationSimilarity;
264495
265281
  exports.lockControlCapability = lockControlCapability;
264496
265282
  exports.logBannerArgs = require_canonical_hash.logBannerArgs;
@@ -264615,6 +265401,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
264615
265401
  exports.resolveEgressDecodeHwAccel = resolveEgressDecodeHwAccel;
264616
265402
  exports.resolveFormat = resolveFormat;
264617
265403
  exports.resolveHydratedFieldValue = require_sleep.resolveHydratedFieldValue;
265404
+ exports.resolveLocationIcon = resolveLocationIcon;
264618
265405
  exports.resolveLocationMode = resolveLocationMode;
264619
265406
  exports.resolveMethodAuth = resolveMethodAuth;
264620
265407
  exports.resolveModelFormat = resolveModelFormat;
@@ -264649,6 +265436,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
264649
265436
  exports.settingsStoreCapability = settingsStoreCapability;
264650
265437
  exports.signalPercentFromBars = signalPercentFromBars;
264651
265438
  exports.signalPercentFromRssi = signalPercentFromRssi;
265439
+ exports.siteZoneKey = siteZoneKey;
264652
265440
  exports.sleep = require_sleep.sleep;
264653
265441
  exports.sleepCancellable = require_sleep.sleepCancellable;
264654
265442
  exports.sliceActiveValue = sliceActiveValue;