@camstack/system 1.2.95 → 1.2.96

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.
@@ -823,11 +823,34 @@ var LINK_MINT_DEADLINE_MS = 2500;
823
823
  var LINK_BATCH_WAIT_MS = 750;
824
824
  /** Frames older than this are never represented as current by the Viewer. */
825
825
  var LINK_CURRENT_MAX_AGE_MS = 15e3;
826
+ /**
827
+ * Memo TTL for the per-device `deviceManager.getDevice` lookup.
828
+ *
829
+ * Every serve decision — each `getSnapshotLinks` target, each media-plane GET,
830
+ * each conditional-GET peek — used to pay this cross-process round trip (plus
831
+ * the battery-slice one) before it could even look at the cache: 21-target
832
+ * grids paid 42 UDS calls per 5 s poll, on top of the captures themselves
833
+ * (live hub, 2026-08-15). Registry facts (name/type/disabled/online/features)
834
+ * change on operator action or provider reconnect, not mid-second, so ten
835
+ * seconds of staleness is invisible next to the poll cadence — and every
836
+ * lifecycle event that changes one of those fields evicts the entry
837
+ * immediately (see the `device.*` subscriptions in `onInitialize`).
838
+ *
839
+ * The BATTERY SLICE is deliberately NOT memoized: sleep/wake transitions must
840
+ * be reactive (a camera that just fell asleep must not be dialed — the
841
+ * Baichuan wake-on-login incident), and `waking` is already live from the
842
+ * in-memory `wakingDevices` map.
843
+ */
844
+ var DEVICE_META_MEMO_MS = 1e4;
826
845
  /** Wake-on-play's 8 s firmware wait + 20 s broker settle, with scheduling
827
846
  * slack. A lost completion event must not leave a camera "Waking" forever. */
828
847
  var SNAPSHOT_WAKING_TTL_MS = 35e3;
829
- /** Default cache window for non-battery cams (seconds). 10s feels live. */
830
- var NON_BATTERY_DEFAULT_MAX_AGE_S = 10;
848
+ /** Default cache window for non-battery cams (seconds). Matches the Viewer's
849
+ * 15 s currency contract (`LINK_CURRENT_MAX_AGE_MS`): a frame younger than
850
+ * that is presented as current, so re-capturing INSIDE the window bought
851
+ * nothing the client would show — under the 5 s links poll it made every
852
+ * other poll recapture the whole fleet through the 6-permit grab pools. */
853
+ var NON_BATTERY_DEFAULT_MAX_AGE_S = 15;
831
854
  /** Default cache window for battery cams (seconds). 1h ≈ "don't wake the cam unless asked". */
832
855
  var BATTERY_DEFAULT_MAX_AGE_S = 3600;
833
856
  /**
@@ -879,6 +902,15 @@ var SnapshotAddon = class SnapshotAddon extends require_dist.BaseAddon {
879
902
  /** Devices inside the explicit battery wake-in-progress window. */
880
903
  wakingDevices = /* @__PURE__ */ new Map();
881
904
  /**
905
+ * Short-TTL memo for `lookupDeviceMeta` (see {@link DEVICE_META_MEMO_MS}).
906
+ * The PROMISE is stored (not the resolved value), so a burst of concurrent
907
+ * callers — a links batch plus the image fetches it mints — joins one
908
+ * in-flight lookup instead of stampeding `deviceManager.getDevice`.
909
+ * `fetchDeviceMeta` never rejects (failure → null), so a settled rejection
910
+ * can never be served from the memo.
911
+ */
912
+ deviceMetaMemo = /* @__PURE__ */ new Map();
913
+ /**
882
914
  * De-dupes concurrent captures per `${deviceId}:${streamId}` and holds a
883
915
  * settled SUCCESS for COALESCE_MS so a grid-mount burst (and a row of refresh
884
916
  * buttons) collapses into one capture instead of one dial per tile.
@@ -949,6 +981,17 @@ var SnapshotAddon = class SnapshotAddon extends require_dist.BaseAddon {
949
981
  const deviceId = event.data.deviceId;
950
982
  if (typeof deviceId === "number") this.evictRemovedDevice(deviceId, "device-unregistered");
951
983
  });
984
+ const dropMetaMemo = (data) => {
985
+ const deviceId = data?.deviceId;
986
+ if (typeof deviceId === "number") this.deviceMetaMemo.delete(deviceId);
987
+ };
988
+ this.subscribe({ category: require_dist.EventCategory.DeviceRegistered }, (event) => dropMetaMemo(event.data));
989
+ this.subscribe({ category: require_dist.EventCategory.DeviceEnabled }, (event) => dropMetaMemo(event.data));
990
+ this.subscribe({ category: require_dist.EventCategory.DeviceDisabled }, (event) => dropMetaMemo(event.data));
991
+ this.subscribe({ category: require_dist.EventCategory.DeviceMetaChanged }, (event) => dropMetaMemo(event.data));
992
+ this.subscribe({ category: require_dist.EventCategory.DeviceOnline }, (event) => dropMetaMemo(event.data));
993
+ this.subscribe({ category: require_dist.EventCategory.DeviceOffline }, (event) => dropMetaMemo(event.data));
994
+ this.subscribe({ category: require_dist.EventCategory.DeviceBindingsChanged }, (event) => dropMetaMemo(event.data));
952
995
  this.subscribe({ category: require_dist.EventCategory.BatteryOnWakeStarted }, (event) => {
953
996
  this.wakingDevices.set(event.data.deviceId, Date.now());
954
997
  });
@@ -1245,7 +1288,7 @@ var SnapshotAddon = class SnapshotAddon extends require_dist.BaseAddon {
1245
1288
  deviceId,
1246
1289
  ...streamId !== void 0 ? { streamId } : {},
1247
1290
  force
1248
- }, 0, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, resolutionMeta);
1291
+ }, 0, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, resolutionMeta, 0);
1249
1292
  if (!image) return null;
1250
1293
  const capturedAt = this.cache.get(deviceId, streamId)?.ts ?? Date.now();
1251
1294
  const prefs = await this.readDeviceSettings(deviceId).catch(() => ({}));
@@ -1350,6 +1393,7 @@ var SnapshotAddon = class SnapshotAddon extends require_dist.BaseAddon {
1350
1393
  this.variants.clear();
1351
1394
  this.stateFrames.clear();
1352
1395
  this.wakingDevices.clear();
1396
+ this.deviceMetaMemo.clear();
1353
1397
  this.captureFlight.clear();
1354
1398
  this.ownerCache = null;
1355
1399
  }
@@ -1382,7 +1426,7 @@ var SnapshotAddon = class SnapshotAddon extends require_dist.BaseAddon {
1382
1426
  })]
1383
1427
  }] });
1384
1428
  }
1385
- async getSnapshot(input, minimumCaptureWaitMs = 0, maximumCacheAgeMs = Number.POSITIVE_INFINITY, maximumCaptureWaitMs = Number.POSITIVE_INFINITY, resolutionMeta) {
1429
+ async getSnapshot(input, minimumCaptureWaitMs = 0, maximumCacheAgeMs = Number.POSITIVE_INFINITY, maximumCaptureWaitMs = Number.POSITIVE_INFINITY, resolutionMeta, staleWaitCapMs = Number.POSITIVE_INFINITY) {
1386
1430
  const { deviceId } = input;
1387
1431
  const force = input.force === true;
1388
1432
  const meta = await this.lookupDeviceMeta(deviceId);
@@ -1445,7 +1489,8 @@ var SnapshotAddon = class SnapshotAddon extends require_dist.BaseAddon {
1445
1489
  log
1446
1490
  }));
1447
1491
  flight.catch(() => void 0);
1448
- const raced = await raceForResult(flight, Math.min(Math.max(decision.waitMs, minimumCaptureWaitMs), maximumCaptureWaitMs));
1492
+ const waitMs = !force && decision.staleFallback && hit !== void 0 ? Math.min(decision.waitMs, staleWaitCapMs) : decision.waitMs;
1493
+ const raced = await raceForResult(flight, Math.min(Math.max(waitMs, minimumCaptureWaitMs), maximumCaptureWaitMs));
1449
1494
  if (raced.settled) try {
1450
1495
  const resolved = await this.resolveOutcome(raced.value, deviceId, hit, log);
1451
1496
  if (resolved !== null) return resolved;
@@ -1745,6 +1790,7 @@ var SnapshotAddon = class SnapshotAddon extends require_dist.BaseAddon {
1745
1790
  this.variants.deleteDevice(deviceId);
1746
1791
  this.stateFrames.delete(deviceId);
1747
1792
  this.wakingDevices.delete(deviceId);
1793
+ this.deviceMetaMemo.delete(deviceId);
1748
1794
  this.captureFlight.invalidatePrefix(`${deviceId}:`);
1749
1795
  }
1750
1796
  /**
@@ -2052,17 +2098,35 @@ var SnapshotAddon = class SnapshotAddon extends require_dist.BaseAddon {
2052
2098
  }
2053
2099
  }
2054
2100
  /**
2055
- * Single-trip device lookup against device-manager. Returns the
2056
- * stable registry fields the wrapper consults. Battery classification is
2057
- * completed by `resolveSnapshotState` from this feature flag PLUS the
2058
- * canonical runtime-state mirror, so a stale persisted feature projection
2059
- * cannot authorize a snapshot dial against a known sleeping battery camera.
2101
+ * Single-trip device lookup against device-manager memoized for
2102
+ * {@link DEVICE_META_MEMO_MS} (promise-keyed, so bursts single-flight).
2103
+ * Registry facts only; nothing battery-runtime lives here, so the TTL never
2104
+ * masks a sleep/wake transition. Announced changes evict immediately (the
2105
+ * `device.*` subscriptions in `onInitialize`); the TTL bounds silent drift.
2106
+ */
2107
+ lookupDeviceMeta(deviceId) {
2108
+ const hit = this.deviceMetaMemo.get(deviceId);
2109
+ if (hit !== void 0 && Date.now() - hit.ts < DEVICE_META_MEMO_MS) return hit.promise;
2110
+ const promise = this.fetchDeviceMeta(deviceId);
2111
+ this.deviceMetaMemo.set(deviceId, {
2112
+ ts: Date.now(),
2113
+ promise
2114
+ });
2115
+ return promise;
2116
+ }
2117
+ /**
2118
+ * The actual `deviceManager.getDevice` round trip behind
2119
+ * {@link lookupDeviceMeta}. Returns the stable registry fields the wrapper
2120
+ * consults. Battery classification is completed by `resolveSnapshotState`
2121
+ * from this feature flag PLUS the canonical runtime-state mirror, so a stale
2122
+ * persisted feature projection cannot authorize a snapshot dial against a
2123
+ * known sleeping battery camera.
2060
2124
  *
2061
2125
  * Logged at debug + null return on failure: every call site already
2062
2126
  * has a sensible fallback path (cache hit, conservative default, …),
2063
2127
  * so we don't want a transient device-manager hiccup to throw.
2064
2128
  */
2065
- async lookupDeviceMeta(deviceId) {
2129
+ async fetchDeviceMeta(deviceId) {
2066
2130
  const api = this.ctx.api;
2067
2131
  if (!api) return null;
2068
2132
  try {
@@ -817,11 +817,34 @@ var LINK_MINT_DEADLINE_MS = 2500;
817
817
  var LINK_BATCH_WAIT_MS = 750;
818
818
  /** Frames older than this are never represented as current by the Viewer. */
819
819
  var LINK_CURRENT_MAX_AGE_MS = 15e3;
820
+ /**
821
+ * Memo TTL for the per-device `deviceManager.getDevice` lookup.
822
+ *
823
+ * Every serve decision — each `getSnapshotLinks` target, each media-plane GET,
824
+ * each conditional-GET peek — used to pay this cross-process round trip (plus
825
+ * the battery-slice one) before it could even look at the cache: 21-target
826
+ * grids paid 42 UDS calls per 5 s poll, on top of the captures themselves
827
+ * (live hub, 2026-08-15). Registry facts (name/type/disabled/online/features)
828
+ * change on operator action or provider reconnect, not mid-second, so ten
829
+ * seconds of staleness is invisible next to the poll cadence — and every
830
+ * lifecycle event that changes one of those fields evicts the entry
831
+ * immediately (see the `device.*` subscriptions in `onInitialize`).
832
+ *
833
+ * The BATTERY SLICE is deliberately NOT memoized: sleep/wake transitions must
834
+ * be reactive (a camera that just fell asleep must not be dialed — the
835
+ * Baichuan wake-on-login incident), and `waking` is already live from the
836
+ * in-memory `wakingDevices` map.
837
+ */
838
+ var DEVICE_META_MEMO_MS = 1e4;
820
839
  /** Wake-on-play's 8 s firmware wait + 20 s broker settle, with scheduling
821
840
  * slack. A lost completion event must not leave a camera "Waking" forever. */
822
841
  var SNAPSHOT_WAKING_TTL_MS = 35e3;
823
- /** Default cache window for non-battery cams (seconds). 10s feels live. */
824
- var NON_BATTERY_DEFAULT_MAX_AGE_S = 10;
842
+ /** Default cache window for non-battery cams (seconds). Matches the Viewer's
843
+ * 15 s currency contract (`LINK_CURRENT_MAX_AGE_MS`): a frame younger than
844
+ * that is presented as current, so re-capturing INSIDE the window bought
845
+ * nothing the client would show — under the 5 s links poll it made every
846
+ * other poll recapture the whole fleet through the 6-permit grab pools. */
847
+ var NON_BATTERY_DEFAULT_MAX_AGE_S = 15;
825
848
  /** Default cache window for battery cams (seconds). 1h ≈ "don't wake the cam unless asked". */
826
849
  var BATTERY_DEFAULT_MAX_AGE_S = 3600;
827
850
  /**
@@ -873,6 +896,15 @@ var SnapshotAddon = class SnapshotAddon extends BaseAddon {
873
896
  /** Devices inside the explicit battery wake-in-progress window. */
874
897
  wakingDevices = /* @__PURE__ */ new Map();
875
898
  /**
899
+ * Short-TTL memo for `lookupDeviceMeta` (see {@link DEVICE_META_MEMO_MS}).
900
+ * The PROMISE is stored (not the resolved value), so a burst of concurrent
901
+ * callers — a links batch plus the image fetches it mints — joins one
902
+ * in-flight lookup instead of stampeding `deviceManager.getDevice`.
903
+ * `fetchDeviceMeta` never rejects (failure → null), so a settled rejection
904
+ * can never be served from the memo.
905
+ */
906
+ deviceMetaMemo = /* @__PURE__ */ new Map();
907
+ /**
876
908
  * De-dupes concurrent captures per `${deviceId}:${streamId}` and holds a
877
909
  * settled SUCCESS for COALESCE_MS so a grid-mount burst (and a row of refresh
878
910
  * buttons) collapses into one capture instead of one dial per tile.
@@ -943,6 +975,17 @@ var SnapshotAddon = class SnapshotAddon extends BaseAddon {
943
975
  const deviceId = event.data.deviceId;
944
976
  if (typeof deviceId === "number") this.evictRemovedDevice(deviceId, "device-unregistered");
945
977
  });
978
+ const dropMetaMemo = (data) => {
979
+ const deviceId = data?.deviceId;
980
+ if (typeof deviceId === "number") this.deviceMetaMemo.delete(deviceId);
981
+ };
982
+ this.subscribe({ category: EventCategory.DeviceRegistered }, (event) => dropMetaMemo(event.data));
983
+ this.subscribe({ category: EventCategory.DeviceEnabled }, (event) => dropMetaMemo(event.data));
984
+ this.subscribe({ category: EventCategory.DeviceDisabled }, (event) => dropMetaMemo(event.data));
985
+ this.subscribe({ category: EventCategory.DeviceMetaChanged }, (event) => dropMetaMemo(event.data));
986
+ this.subscribe({ category: EventCategory.DeviceOnline }, (event) => dropMetaMemo(event.data));
987
+ this.subscribe({ category: EventCategory.DeviceOffline }, (event) => dropMetaMemo(event.data));
988
+ this.subscribe({ category: EventCategory.DeviceBindingsChanged }, (event) => dropMetaMemo(event.data));
946
989
  this.subscribe({ category: EventCategory.BatteryOnWakeStarted }, (event) => {
947
990
  this.wakingDevices.set(event.data.deviceId, Date.now());
948
991
  });
@@ -1239,7 +1282,7 @@ var SnapshotAddon = class SnapshotAddon extends BaseAddon {
1239
1282
  deviceId,
1240
1283
  ...streamId !== void 0 ? { streamId } : {},
1241
1284
  force
1242
- }, 0, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, resolutionMeta);
1285
+ }, 0, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, resolutionMeta, 0);
1243
1286
  if (!image) return null;
1244
1287
  const capturedAt = this.cache.get(deviceId, streamId)?.ts ?? Date.now();
1245
1288
  const prefs = await this.readDeviceSettings(deviceId).catch(() => ({}));
@@ -1344,6 +1387,7 @@ var SnapshotAddon = class SnapshotAddon extends BaseAddon {
1344
1387
  this.variants.clear();
1345
1388
  this.stateFrames.clear();
1346
1389
  this.wakingDevices.clear();
1390
+ this.deviceMetaMemo.clear();
1347
1391
  this.captureFlight.clear();
1348
1392
  this.ownerCache = null;
1349
1393
  }
@@ -1376,7 +1420,7 @@ var SnapshotAddon = class SnapshotAddon extends BaseAddon {
1376
1420
  })]
1377
1421
  }] });
1378
1422
  }
1379
- async getSnapshot(input, minimumCaptureWaitMs = 0, maximumCacheAgeMs = Number.POSITIVE_INFINITY, maximumCaptureWaitMs = Number.POSITIVE_INFINITY, resolutionMeta) {
1423
+ async getSnapshot(input, minimumCaptureWaitMs = 0, maximumCacheAgeMs = Number.POSITIVE_INFINITY, maximumCaptureWaitMs = Number.POSITIVE_INFINITY, resolutionMeta, staleWaitCapMs = Number.POSITIVE_INFINITY) {
1380
1424
  const { deviceId } = input;
1381
1425
  const force = input.force === true;
1382
1426
  const meta = await this.lookupDeviceMeta(deviceId);
@@ -1439,7 +1483,8 @@ var SnapshotAddon = class SnapshotAddon extends BaseAddon {
1439
1483
  log
1440
1484
  }));
1441
1485
  flight.catch(() => void 0);
1442
- const raced = await raceForResult(flight, Math.min(Math.max(decision.waitMs, minimumCaptureWaitMs), maximumCaptureWaitMs));
1486
+ const waitMs = !force && decision.staleFallback && hit !== void 0 ? Math.min(decision.waitMs, staleWaitCapMs) : decision.waitMs;
1487
+ const raced = await raceForResult(flight, Math.min(Math.max(waitMs, minimumCaptureWaitMs), maximumCaptureWaitMs));
1443
1488
  if (raced.settled) try {
1444
1489
  const resolved = await this.resolveOutcome(raced.value, deviceId, hit, log);
1445
1490
  if (resolved !== null) return resolved;
@@ -1739,6 +1784,7 @@ var SnapshotAddon = class SnapshotAddon extends BaseAddon {
1739
1784
  this.variants.deleteDevice(deviceId);
1740
1785
  this.stateFrames.delete(deviceId);
1741
1786
  this.wakingDevices.delete(deviceId);
1787
+ this.deviceMetaMemo.delete(deviceId);
1742
1788
  this.captureFlight.invalidatePrefix(`${deviceId}:`);
1743
1789
  }
1744
1790
  /**
@@ -2046,17 +2092,35 @@ var SnapshotAddon = class SnapshotAddon extends BaseAddon {
2046
2092
  }
2047
2093
  }
2048
2094
  /**
2049
- * Single-trip device lookup against device-manager. Returns the
2050
- * stable registry fields the wrapper consults. Battery classification is
2051
- * completed by `resolveSnapshotState` from this feature flag PLUS the
2052
- * canonical runtime-state mirror, so a stale persisted feature projection
2053
- * cannot authorize a snapshot dial against a known sleeping battery camera.
2095
+ * Single-trip device lookup against device-manager memoized for
2096
+ * {@link DEVICE_META_MEMO_MS} (promise-keyed, so bursts single-flight).
2097
+ * Registry facts only; nothing battery-runtime lives here, so the TTL never
2098
+ * masks a sleep/wake transition. Announced changes evict immediately (the
2099
+ * `device.*` subscriptions in `onInitialize`); the TTL bounds silent drift.
2100
+ */
2101
+ lookupDeviceMeta(deviceId) {
2102
+ const hit = this.deviceMetaMemo.get(deviceId);
2103
+ if (hit !== void 0 && Date.now() - hit.ts < DEVICE_META_MEMO_MS) return hit.promise;
2104
+ const promise = this.fetchDeviceMeta(deviceId);
2105
+ this.deviceMetaMemo.set(deviceId, {
2106
+ ts: Date.now(),
2107
+ promise
2108
+ });
2109
+ return promise;
2110
+ }
2111
+ /**
2112
+ * The actual `deviceManager.getDevice` round trip behind
2113
+ * {@link lookupDeviceMeta}. Returns the stable registry fields the wrapper
2114
+ * consults. Battery classification is completed by `resolveSnapshotState`
2115
+ * from this feature flag PLUS the canonical runtime-state mirror, so a stale
2116
+ * persisted feature projection cannot authorize a snapshot dial against a
2117
+ * known sleeping battery camera.
2054
2118
  *
2055
2119
  * Logged at debug + null return on failure: every call site already
2056
2120
  * has a sensible fallback path (cache hit, conservative default, …),
2057
2121
  * so we don't want a transient device-manager hiccup to throw.
2058
2122
  */
2059
- async lookupDeviceMeta(deviceId) {
2123
+ async fetchDeviceMeta(deviceId) {
2060
2124
  const api = this.ctx.api;
2061
2125
  if (!api) return null;
2062
2126
  try {
@@ -48,6 +48,15 @@ export declare class SnapshotAddon extends BaseAddon<SnapshotAddonConfig> {
48
48
  private readonly stateFrames;
49
49
  /** Devices inside the explicit battery wake-in-progress window. */
50
50
  private readonly wakingDevices;
51
+ /**
52
+ * Short-TTL memo for `lookupDeviceMeta` (see {@link DEVICE_META_MEMO_MS}).
53
+ * The PROMISE is stored (not the resolved value), so a burst of concurrent
54
+ * callers — a links batch plus the image fetches it mints — joins one
55
+ * in-flight lookup instead of stampeding `deviceManager.getDevice`.
56
+ * `fetchDeviceMeta` never rejects (failure → null), so a settled rejection
57
+ * can never be served from the memo.
58
+ */
59
+ private readonly deviceMetaMemo;
51
60
  /**
52
61
  * De-dupes concurrent captures per `${deviceId}:${streamId}` and holds a
53
62
  * settled SUCCESS for COALESCE_MS so a grid-mount burst (and a row of refresh
@@ -433,17 +442,26 @@ export declare class SnapshotAddon extends BaseAddon<SnapshotAddonConfig> {
433
442
  */
434
443
  private stateImage;
435
444
  /**
436
- * Single-trip device lookup against device-manager. Returns the
437
- * stable registry fields the wrapper consults. Battery classification is
438
- * completed by `resolveSnapshotState` from this feature flag PLUS the
439
- * canonical runtime-state mirror, so a stale persisted feature projection
440
- * cannot authorize a snapshot dial against a known sleeping battery camera.
445
+ * Single-trip device lookup against device-manager memoized for
446
+ * {@link DEVICE_META_MEMO_MS} (promise-keyed, so bursts single-flight).
447
+ * Registry facts only; nothing battery-runtime lives here, so the TTL never
448
+ * masks a sleep/wake transition. Announced changes evict immediately (the
449
+ * `device.*` subscriptions in `onInitialize`); the TTL bounds silent drift.
450
+ */
451
+ private lookupDeviceMeta;
452
+ /**
453
+ * The actual `deviceManager.getDevice` round trip behind
454
+ * {@link lookupDeviceMeta}. Returns the stable registry fields the wrapper
455
+ * consults. Battery classification is completed by `resolveSnapshotState`
456
+ * from this feature flag PLUS the canonical runtime-state mirror, so a stale
457
+ * persisted feature projection cannot authorize a snapshot dial against a
458
+ * known sleeping battery camera.
441
459
  *
442
460
  * Logged at debug + null return on failure: every call site already
443
461
  * has a sensible fallback path (cache hit, conservative default, …),
444
462
  * so we don't want a transient device-manager hiccup to throw.
445
463
  */
446
- private lookupDeviceMeta;
464
+ private fetchDeviceMeta;
447
465
  /** Settings-UI helper — battery flag drives the default max-age in the field description. */
448
466
  private isDeviceBattery;
449
467
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.2.95",
3
+ "version": "1.2.96",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",